beast-agent 2.44.12 → 2.44.13
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 +25 -0
- package/src/main.js +14 -0
- package/src/renderer/i18n.js +18 -0
- package/src/renderer/renderer.js +198 -125
- package/src/renderer/style.css +57 -0
package/package.json
CHANGED
package/src/agent/piper.js
CHANGED
|
@@ -195,6 +195,29 @@ async function install(voiceId) {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/* ---------- sentez ayarları (hız/duraklama/ifade) ----------
|
|
199
|
+
speed: 1.0 normal; >1 hızlı, <1 yavaş → CLI'da --length_scale 1/speed
|
|
200
|
+
(piper'da length_scale büyükse konuşma YAVAŞLAR)
|
|
201
|
+
silence: cümle sonu duraklama sn (--sentence_silence, varsayılan 0.2)
|
|
202
|
+
noiseScale: ifade/doğallık (--noise_scale, varsayılan 0.667; düşük=daha
|
|
203
|
+
stabil/robotik, yüksek=daha canlı ama artefakt riski) */
|
|
204
|
+
function clampNum(n, lo, hi) {
|
|
205
|
+
return Math.max(lo, Math.min(hi, n));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function tuningArgs(opts = {}) {
|
|
209
|
+
const args = [];
|
|
210
|
+
const speed = Number(opts.speed);
|
|
211
|
+
if (Number.isFinite(speed) && Math.abs(speed - 1) > 0.001) {
|
|
212
|
+
args.push('--length_scale', String(+(1 / clampNum(speed, 0.5, 2)).toFixed(3)));
|
|
213
|
+
}
|
|
214
|
+
const sil = Number(opts.sentenceSilence);
|
|
215
|
+
if (Number.isFinite(sil)) args.push('--sentence_silence', String(+(clampNum(sil, 0, 2)).toFixed(3)));
|
|
216
|
+
const noise = Number(opts.noiseScale);
|
|
217
|
+
if (Number.isFinite(noise)) args.push('--noise_scale', String(+(clampNum(noise, 0, 1.5)).toFixed(3)));
|
|
218
|
+
return args;
|
|
219
|
+
}
|
|
220
|
+
|
|
198
221
|
/* ---------- sentez: metin → { audio: WAV buffer, mime } ----------
|
|
199
222
|
DİKKAT: piper'ı ASLA stdout'a yazdırmıyoruz (--output_file -). Windows'ta
|
|
200
223
|
binary stdout akışı bozuluyor — çıktı gürültülü/cızırtılı geliyor
|
|
@@ -215,6 +238,7 @@ function synthesize(text, opts = {}) {
|
|
|
215
238
|
'--model', voicePath(vid),
|
|
216
239
|
'--config', voiceJsonPath(vid),
|
|
217
240
|
'--output_file', outFile,
|
|
241
|
+
...tuningArgs(opts),
|
|
218
242
|
'-q',
|
|
219
243
|
];
|
|
220
244
|
const p = spawn(exePath(), args, { windowsHide: true, cwd: binDir() });
|
|
@@ -250,6 +274,7 @@ module.exports = {
|
|
|
250
274
|
synthesize,
|
|
251
275
|
install,
|
|
252
276
|
status,
|
|
277
|
+
tuningArgs,
|
|
253
278
|
ensureRuntime,
|
|
254
279
|
ensureVoice,
|
|
255
280
|
runtimeInstalled,
|
package/src/main.js
CHANGED
|
@@ -1060,6 +1060,9 @@ async function synthesizeSpeech(text) {
|
|
|
1060
1060
|
if (engine === 'piper') {
|
|
1061
1061
|
return await piper.synthesize(String(text).slice(0, 4000), {
|
|
1062
1062
|
voice: cfg.piperVoice || piper.DEFAULT_VOICE,
|
|
1063
|
+
speed: cfg.piperSpeed,
|
|
1064
|
+
sentenceSilence: cfg.piperSilence,
|
|
1065
|
+
noiseScale: cfg.piperNoise,
|
|
1063
1066
|
});
|
|
1064
1067
|
}
|
|
1065
1068
|
if (!cfg.baseUrl || !cfg.key) return null;
|
|
@@ -12879,6 +12882,13 @@ ipcMain.handle('wa:queue:get', () => mqueue.stats());
|
|
|
12879
12882
|
|
|
12880
12883
|
ipcMain.handle('wa:sessions', () => [...waChats.values()]);
|
|
12881
12884
|
|
|
12885
|
+
/* TTS sayısal ayar kelepçesi: geçersiz/boş değerde varsayılana döner */
|
|
12886
|
+
function clampTtsNum(v, lo, hi, def) {
|
|
12887
|
+
const n = Number(v);
|
|
12888
|
+
if (!Number.isFinite(n)) return def;
|
|
12889
|
+
return Math.max(lo, Math.min(hi, n));
|
|
12890
|
+
}
|
|
12891
|
+
|
|
12882
12892
|
ipcMain.handle('wa:tts:get', () => settings.waTts || {});
|
|
12883
12893
|
ipcMain.handle('wa:tts:set', (_e, cfg) => {
|
|
12884
12894
|
const eng = String((cfg && cfg.engine) || 'edge');
|
|
@@ -12889,6 +12899,10 @@ ipcMain.handle('wa:tts:set', (_e, cfg) => {
|
|
|
12889
12899
|
engine: ['edge', 'piper', 'openai'].includes(eng) ? eng : 'edge',
|
|
12890
12900
|
edgeVoice: String((cfg && cfg.edgeVoice) || 'tr-TR-AhmetNeural').trim(),
|
|
12891
12901
|
piperVoice: piper.VOICES[pv] ? pv : piper.DEFAULT_VOICE,
|
|
12902
|
+
/* Piper ince ayar: hız (0.6-1.6), cümle sonu duraklama (0-1 sn), ifade (0-1.2) */
|
|
12903
|
+
piperSpeed: clampTtsNum(cfg && cfg.piperSpeed, 0.6, 1.6, 1),
|
|
12904
|
+
piperSilence: clampTtsNum(cfg && cfg.piperSilence, 0, 1, 0.2),
|
|
12905
|
+
piperNoise: clampTtsNum(cfg && cfg.piperNoise, 0, 1.2, 0.667),
|
|
12892
12906
|
chatAutoSpeak: !!(cfg && cfg.chatAutoSpeak),
|
|
12893
12907
|
baseUrl: String((cfg && cfg.baseUrl) || '').trim(),
|
|
12894
12908
|
key: String((cfg && cfg.key) || '').trim(),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -269,6 +269,12 @@
|
|
|
269
269
|
tts_test: 'Test sesi çal',
|
|
270
270
|
stt_download_now: 'Şimdi indir / hazırla',
|
|
271
271
|
stt_engine: 'STT motoru',
|
|
272
|
+
stt_seg_auto: 'Otomatik',
|
|
273
|
+
stt_seg_whisper: 'Whisper (yerel)',
|
|
274
|
+
tts_stt_title: 'Ses → Yazı',
|
|
275
|
+
tts_stt_sub: 'Mikrofon kaydını yazıya çeviren motor',
|
|
276
|
+
tts_tts_title: 'Yazı → Ses',
|
|
277
|
+
tts_tts_sub: 'Sesli yanıt motoru ve ses seçimi',
|
|
272
278
|
stt_engine_auto: 'Otomatik — anahtar varsa bulut, yoksa yerel Whisper',
|
|
273
279
|
stt_engine_local: 'Whisper — yerel/offline (large-v3-turbo)',
|
|
274
280
|
stt_engine_groq: 'Groq Whisper — bulut (hızlı)',
|
|
@@ -278,6 +284,9 @@
|
|
|
278
284
|
tts_edge_voice: 'Edge sesi',
|
|
279
285
|
tts_piper_voice: 'Piper sesi',
|
|
280
286
|
tts_piper_download: 'Piper\'ı indir / hazırla',
|
|
287
|
+
tts_piper_speed: 'Konuşma hızı',
|
|
288
|
+
tts_piper_silence: 'Cümle sonu duraklama',
|
|
289
|
+
tts_piper_noise: 'İfade (doğallık)',
|
|
281
290
|
tts_chat_auto: 'Chat\'te ajanın yazdıklarını otomatik seslendir',
|
|
282
291
|
tts_on: 'TTS açık — cevaplar sesli de gider',
|
|
283
292
|
tts_off: 'TTS kapalı',
|
|
@@ -1000,6 +1009,12 @@
|
|
|
1000
1009
|
tts_test: 'Play test sound',
|
|
1001
1010
|
stt_download_now: 'Download / prepare now',
|
|
1002
1011
|
stt_engine: 'STT engine',
|
|
1012
|
+
stt_seg_auto: 'Auto',
|
|
1013
|
+
stt_seg_whisper: 'Whisper (local)',
|
|
1014
|
+
tts_stt_title: 'Speech → Text',
|
|
1015
|
+
tts_stt_sub: 'Engine that transcribes your microphone',
|
|
1016
|
+
tts_tts_title: 'Text → Speech',
|
|
1017
|
+
tts_tts_sub: 'Voice reply engine and voice',
|
|
1003
1018
|
stt_engine_auto: 'Auto — cloud if a key exists, otherwise local Whisper',
|
|
1004
1019
|
stt_engine_local: 'Whisper — local/offline (large-v3-turbo)',
|
|
1005
1020
|
stt_engine_groq: 'Groq Whisper — cloud (fast)',
|
|
@@ -1009,6 +1024,9 @@
|
|
|
1009
1024
|
tts_edge_voice: 'Edge voice',
|
|
1010
1025
|
tts_piper_voice: 'Piper voice',
|
|
1011
1026
|
tts_piper_download: 'Download / prepare Piper',
|
|
1027
|
+
tts_piper_speed: 'Speaking speed',
|
|
1028
|
+
tts_piper_silence: 'Sentence pause',
|
|
1029
|
+
tts_piper_noise: 'Expressiveness',
|
|
1012
1030
|
tts_chat_auto: 'Auto-speak agent replies in chat',
|
|
1013
1031
|
tts_on: 'TTS on — replies also go as voice',
|
|
1014
1032
|
tts_off: 'TTS off',
|
package/src/renderer/renderer.js
CHANGED
|
@@ -2554,178 +2554,251 @@ function updateInstallPct(ev) {
|
|
|
2554
2554
|
} catch {}
|
|
2555
2555
|
}
|
|
2556
2556
|
|
|
2557
|
-
async function renderTtsPane() {
|
|
2557
|
+
async function renderTtsPane() {
|
|
2558
|
+
const pane = $('#tab-tts');
|
|
2558
2559
|
if (!pane) return;
|
|
2559
2560
|
/* STT DURUMU: model indirildi mi / iniyor mu / hangi motor */
|
|
2560
2561
|
let stt = null;
|
|
2561
2562
|
try { stt = await beast.sttStatus(); } catch {}
|
|
2562
|
-
const
|
|
2563
|
-
ready: 'HAZIR — model yüklü',
|
|
2564
|
-
loading: 'İNİYOR / yükleniyor…',
|
|
2565
|
-
downloaded: 'indirildi — ilk kullanımda yüklenir',
|
|
2566
|
-
partial: 'kısmen indi — devam edecek',
|
|
2567
|
-
missing: 'henüz indirilmedi',
|
|
2568
|
-
cloud: 'bulut motoru — indirme gerekmez',
|
|
2569
|
-
};
|
|
2570
|
-
const sttLine = stt
|
|
2571
|
-
? stt.engineLabel + ' · ' + (stateText[stt.state] || stt.state) + (stt.mb ? ' · ' + stt.mb + ' MB' : '')
|
|
2572
|
-
: (window.beast ? 'bilinmiyor' : '');
|
|
2573
|
-
const edgeOpts = [
|
|
2563
|
+
const EDGE_VOICES = [
|
|
2574
2564
|
['tr-TR-AhmetNeural', 'Ahmet — Türkçe (erkek)'],
|
|
2575
2565
|
['tr-TR-EmelNeural', 'Emel — Türkçe (kadın)'],
|
|
2576
2566
|
['en-US-GuyNeural', 'Guy — English (male)'],
|
|
2577
2567
|
['en-US-AriaNeural', 'Aria — English (female)'],
|
|
2578
2568
|
['de-DE-KatjaNeural', 'Katja — Deutsch'],
|
|
2579
2569
|
['ar-SA-HamedNeural', 'Hamed — العربية'],
|
|
2580
|
-
]
|
|
2581
|
-
|
|
2582
|
-
.join('');
|
|
2583
|
-
const piperOpts = [
|
|
2570
|
+
];
|
|
2571
|
+
const PIPER_VOICES = [
|
|
2584
2572
|
['tr_TR-fahrettin-medium', 'Fahrettin — Türkçe (erkek)'],
|
|
2585
2573
|
['tr_TR-fettah-medium', 'Fettah — Türkçe (erkek)'],
|
|
2586
2574
|
['en_US-lessac-medium', 'Lessac — English (male)'],
|
|
2587
2575
|
['en_US-amy-medium', 'Amy — English (female)'],
|
|
2588
|
-
]
|
|
2589
|
-
|
|
2590
|
-
.join('')
|
|
2576
|
+
];
|
|
2577
|
+
const seg = (name, items) => '<div class="voice-seg" id="' + name + '">' +
|
|
2578
|
+
items.map(([v, n]) => `<label><input type="radio" name="${name}" value="${v}"/><span>${n}</span></label>`).join('') +
|
|
2579
|
+
'</div>';
|
|
2591
2580
|
pane.innerHTML =
|
|
2592
2581
|
'<h2>' + _t('tts_h2') + '</h2><div class="sub">' + _t('tts_sub') + '</div>' +
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
<
|
|
2609
|
-
|
|
2610
|
-
<
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
<
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
<input id="
|
|
2627
|
-
<input id="
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
<
|
|
2631
|
-
|
|
2632
|
-
|
|
2582
|
+
`<div class="voice-card" style="margin-top:14px">
|
|
2583
|
+
<div class="voice-head">
|
|
2584
|
+
<div class="voice-ico">🎤</div>
|
|
2585
|
+
<div>
|
|
2586
|
+
<div class="voice-title">${_t('tts_stt_title')}</div>
|
|
2587
|
+
<div class="voice-sub">${_t('tts_stt_sub')}</div>
|
|
2588
|
+
</div>
|
|
2589
|
+
</div>
|
|
2590
|
+
${seg('sttEngine', [['', _t('stt_seg_auto')], ['local', _t('stt_seg_whisper')], ['groq', 'Groq'], ['openai', 'OpenAI']])}
|
|
2591
|
+
<div class="voice-status">
|
|
2592
|
+
<span id="sttDot" class="voice-dot"></span><span id="sttStatusTxt"></span>
|
|
2593
|
+
<button id="sttDlBtn" class="btn ghost" style="margin-left:auto">${_t('stt_download_now')}</button>
|
|
2594
|
+
</div>
|
|
2595
|
+
</div>
|
|
2596
|
+
<div class="voice-card">
|
|
2597
|
+
<div class="voice-head">
|
|
2598
|
+
<div class="voice-ico">🔊</div>
|
|
2599
|
+
<div>
|
|
2600
|
+
<div class="voice-title">${_t('tts_tts_title')}</div>
|
|
2601
|
+
<div class="voice-sub">${_t('tts_tts_sub')}</div>
|
|
2602
|
+
</div>
|
|
2603
|
+
<label class="lock-row" style="margin-left:auto"><input type="checkbox" id="ttsOn" /><span>${_t('tts_active')}</span></label>
|
|
2604
|
+
</div>
|
|
2605
|
+
${seg('ttsEngine', [['edge', 'Edge'], ['piper', 'Piper'], ['openai', 'OpenAI']])}
|
|
2606
|
+
<div class="voice-row">
|
|
2607
|
+
<select id="ttsVoiceSel" class="inp"></select>
|
|
2608
|
+
</div>
|
|
2609
|
+
<label class="lock-row" style="margin-top:10px"><input type="checkbox" id="ttsChatAuto" /><span>${_t('tts_chat_auto')}</span></label>
|
|
2610
|
+
<div class="voice-status" id="piperStatusRow">
|
|
2611
|
+
<span id="piperDot" class="voice-dot"></span><span id="piperStatusTxt"></span>
|
|
2612
|
+
<button id="piperDlBtn" class="btn ghost" style="margin-left:auto">${_t('tts_piper_download')}</button>
|
|
2613
|
+
</div>
|
|
2614
|
+
<div class="voice-sliders" id="piperAdvWrap">
|
|
2615
|
+
<div class="voice-slider"><span>${_t('tts_piper_speed')}</span><input id="piperSpeed" type="range" min="0.6" max="1.6" step="0.05" value="1"/><b id="piperSpeedVal">1.00x</b></div>
|
|
2616
|
+
<div class="voice-slider"><span>${_t('tts_piper_silence')}</span><input id="piperSil" type="range" min="0" max="1" step="0.05" value="0.2"/><b id="piperSilVal">0.20s</b></div>
|
|
2617
|
+
<div class="voice-slider"><span>${_t('tts_piper_noise')}</span><input id="piperNoise" type="range" min="0" max="1.2" step="0.05" value="0.667"/><b id="piperNoiseVal">0.67</b></div>
|
|
2618
|
+
</div>
|
|
2619
|
+
<div id="ttsOpenaiWrap" style="display:none">
|
|
2620
|
+
<div class="voice-row" style="margin-top:10px">
|
|
2621
|
+
<input id="ttsUrl" class="inp" placeholder="https://api.openai.com/v1" autocomplete="off" />
|
|
2622
|
+
<input id="ttsKey" class="inp" type="password" placeholder="API Key" autocomplete="off" />
|
|
2623
|
+
</div>
|
|
2624
|
+
<div class="voice-row">
|
|
2625
|
+
<input id="ttsModel" class="inp" placeholder="tts-1" autocomplete="off" />
|
|
2626
|
+
<input id="ttsVoice" class="inp" placeholder="alloy" autocomplete="off" />
|
|
2627
|
+
</div>
|
|
2628
|
+
</div>
|
|
2629
|
+
<div class="voice-actions">
|
|
2630
|
+
<button id="ttsSave" class="btn">${_t('tts_save')}</button>
|
|
2631
|
+
<button id="ttsTest" class="btn ghost">${_t('tts_test')}</button>
|
|
2632
|
+
</div>
|
|
2633
|
+
<div class="voice-note">${_t('tts_note')}</div>
|
|
2634
|
+
</div>`;
|
|
2635
|
+
|
|
2636
|
+
/* ---------- yardımcılar ---------- */
|
|
2637
|
+
const ttsEng = () => (document.querySelector('input[name="ttsEngine"]:checked') || {}).value || 'edge';
|
|
2638
|
+
const setSeg = (name, val) => {
|
|
2639
|
+
document.querySelectorAll('input[name="' + name + '"]').forEach((r) => {
|
|
2640
|
+
r.checked = r.value === String(val);
|
|
2641
|
+
const lab = r.closest('label');
|
|
2642
|
+
if (lab) lab.classList.toggle('on', r.checked);
|
|
2643
|
+
});
|
|
2644
|
+
};
|
|
2645
|
+
const refreshSeg = (name) => {
|
|
2646
|
+
document.querySelectorAll('input[name="' + name + '"]').forEach((r) => {
|
|
2647
|
+
const lab = r.closest('label');
|
|
2648
|
+
if (lab) lab.classList.toggle('on', r.checked);
|
|
2649
|
+
});
|
|
2650
|
+
};
|
|
2651
|
+
const sttName = (st) => (st.provider === 'groq' ? 'Groq Whisper' : st.provider === 'openai' ? 'Whisper (API)' : 'Whisper (yerel)');
|
|
2652
|
+
const sttState = {
|
|
2653
|
+
ready: 'hazır',
|
|
2654
|
+
loading: 'iniyor…',
|
|
2655
|
+
downloaded: 'hazır (ilk kullanımda yüklenir)',
|
|
2656
|
+
partial: 'kısmen indi',
|
|
2657
|
+
missing: 'indirilmedi',
|
|
2658
|
+
cloud: 'bulut',
|
|
2659
|
+
};
|
|
2660
|
+
|
|
2661
|
+
/* ---------- ses listeleri (motor başına) ---------- */
|
|
2662
|
+
let edgeVoice = 'tr-TR-AhmetNeural';
|
|
2663
|
+
let piperVoice = 'tr_TR-fahrettin-medium';
|
|
2664
|
+
const fillVoices = () => {
|
|
2665
|
+
const eng = ttsEng();
|
|
2666
|
+
const sel = $('#ttsVoiceSel');
|
|
2667
|
+
if (!sel) return;
|
|
2668
|
+
const list = eng === 'piper' ? PIPER_VOICES : EDGE_VOICES;
|
|
2669
|
+
sel.innerHTML = list.map(([v, n]) => `<option value="${v}">${n}</option>`).join('');
|
|
2670
|
+
sel.value = eng === 'piper' ? piperVoice : edgeVoice;
|
|
2671
|
+
sel.style.display = eng === 'openai' ? 'none' : '';
|
|
2672
|
+
};
|
|
2673
|
+
const captureVoice = () => {
|
|
2674
|
+
const eng = ttsEng();
|
|
2675
|
+
const sel = $('#ttsVoiceSel');
|
|
2676
|
+
if (!sel) return;
|
|
2677
|
+
if (eng === 'piper') piperVoice = sel.value || piperVoice;
|
|
2678
|
+
else if (eng === 'edge') edgeVoice = sel.value || edgeVoice;
|
|
2679
|
+
};
|
|
2633
2680
|
|
|
2634
2681
|
const syncEngineUi = () => {
|
|
2635
|
-
const eng =
|
|
2636
|
-
const isEdge = eng === 'edge';
|
|
2682
|
+
const eng = ttsEng();
|
|
2637
2683
|
const isPiper = eng === 'piper';
|
|
2638
|
-
$('#
|
|
2639
|
-
$('#
|
|
2640
|
-
$('#
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2684
|
+
$('#piperStatusRow').style.display = isPiper ? '' : 'none';
|
|
2685
|
+
$('#piperAdvWrap').style.display = isPiper ? '' : 'none';
|
|
2686
|
+
$('#ttsOpenaiWrap').style.display = eng === 'openai' ? '' : 'none';
|
|
2687
|
+
};
|
|
2688
|
+
|
|
2689
|
+
/* Piper ince ayar etiketleri (hız/duraklama/ifade) */
|
|
2690
|
+
const syncPiperLabels = () => {
|
|
2691
|
+
const sv = $('#piperSpeedVal'), lv = $('#piperSilVal'), nv = $('#piperNoiseVal');
|
|
2692
|
+
if (sv) sv.textContent = Number($('#piperSpeed').value).toFixed(2) + 'x';
|
|
2693
|
+
if (lv) lv.textContent = Number($('#piperSil').value).toFixed(2) + 's';
|
|
2694
|
+
if (nv) nv.textContent = Number($('#piperNoise').value).toFixed(2);
|
|
2695
|
+
};
|
|
2696
|
+
|
|
2697
|
+
/* ---------- durum satırları ---------- */
|
|
2698
|
+
const refreshPiperStatus = async () => {
|
|
2699
|
+
try {
|
|
2700
|
+
const st = await beast.piperStatus(piperVoice);
|
|
2701
|
+
const txt = $('#piperStatusTxt'), dot = $('#piperDot'), btn = $('#piperDlBtn');
|
|
2702
|
+
if (!st) return null;
|
|
2703
|
+
if (txt) txt.textContent = st.installed ? 'Hazır — yerel/offline' : st.installing ? 'İndiriliyor…' : 'İndirilmedi (~80MB)';
|
|
2704
|
+
if (dot) dot.className = 'voice-dot' + (st.installed ? ' ok' : st.installing ? ' warn' : '');
|
|
2705
|
+
if (btn) btn.disabled = !!st.installed;
|
|
2706
|
+
return st;
|
|
2707
|
+
} catch { return null; }
|
|
2708
|
+
};
|
|
2709
|
+
const refreshSttStatus = async () => {
|
|
2710
|
+
try {
|
|
2711
|
+
stt = await beast.sttStatus();
|
|
2712
|
+
const txt = $('#sttStatusTxt'), dot = $('#sttDot');
|
|
2713
|
+
if (txt && stt) txt.textContent = sttName(stt) + ' · ' + (sttState[stt.state] || stt.state) + (stt.mb ? ' · ' + stt.mb + ' MB' : '');
|
|
2714
|
+
if (dot && stt) {
|
|
2715
|
+
dot.className = 'voice-dot' + (
|
|
2716
|
+
['ready', 'cloud', 'downloaded'].includes(stt.state) ? ' ok' :
|
|
2717
|
+
['loading', 'partial'].includes(stt.state) ? ' warn' : ''
|
|
2718
|
+
);
|
|
2719
|
+
}
|
|
2720
|
+
return stt;
|
|
2721
|
+
} catch { return null; }
|
|
2644
2722
|
};
|
|
2645
2723
|
|
|
2724
|
+
/* ---------- ayarları yükle ---------- */
|
|
2646
2725
|
try {
|
|
2647
2726
|
const tts = await beast.waGetTts();
|
|
2648
2727
|
$('#ttsOn').checked = !!tts.enabled;
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
$('#
|
|
2728
|
+
edgeVoice = tts.edgeVoice || edgeVoice;
|
|
2729
|
+
piperVoice = tts.piperVoice || piperVoice;
|
|
2730
|
+
$('#piperSpeed').value = Number.isFinite(Number(tts.piperSpeed)) ? tts.piperSpeed : 1;
|
|
2731
|
+
$('#piperSil').value = Number.isFinite(Number(tts.piperSilence)) ? tts.piperSilence : 0.2;
|
|
2732
|
+
$('#piperNoise').value = Number.isFinite(Number(tts.piperNoise)) ? tts.piperNoise : 0.667;
|
|
2652
2733
|
$('#ttsChatAuto').checked = !!tts.chatAutoSpeak;
|
|
2653
2734
|
$('#ttsUrl').value = tts.baseUrl || '';
|
|
2654
2735
|
$('#ttsKey').value = tts.key || '';
|
|
2655
2736
|
$('#ttsModel').value = tts.model || 'tts-1';
|
|
2656
2737
|
$('#ttsVoice').value = tts.voice || 'alloy';
|
|
2738
|
+
setSeg('ttsEngine', ['edge', 'piper', 'openai'].includes(tts.engine) ? tts.engine : 'edge');
|
|
2657
2739
|
} catch {}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
if (sttEngineSel) sttEngineSel.value = (stt && stt.pref) || '';
|
|
2662
|
-
|
|
2663
|
-
/* Piper durum satırı: runtime/ses modeli hazır mı (indirilince güncellenir) */
|
|
2664
|
-
const refreshPiperStatus = async () => {
|
|
2665
|
-
const v = $('#ttsPiperVoice') ? $('#ttsPiperVoice').value : '';
|
|
2666
|
-
try {
|
|
2667
|
-
const st = await beast.piperStatus(v);
|
|
2668
|
-
const el = $('#piperStatusTxt');
|
|
2669
|
-
if (el && st) {
|
|
2670
|
-
el.textContent = 'Piper: ' + (st.installed
|
|
2671
|
-
? 'hazır — yerel/offline'
|
|
2672
|
-
: st.installing
|
|
2673
|
-
? 'indiriliyor…'
|
|
2674
|
-
: st.runtime
|
|
2675
|
-
? 'ses modeli indirilecek (ilk kullanımda otomatik)'
|
|
2676
|
-
: 'runtime + ses modeli indirilecek (ilk kullanımda otomatik)');
|
|
2677
|
-
}
|
|
2678
|
-
return st;
|
|
2679
|
-
} catch { return null; }
|
|
2680
|
-
};
|
|
2681
|
-
|
|
2740
|
+
setSeg('sttEngine', (stt && stt.pref) || '');
|
|
2741
|
+
syncPiperLabels();
|
|
2742
|
+
fillVoices();
|
|
2682
2743
|
syncEngineUi();
|
|
2683
2744
|
await refreshPiperStatus();
|
|
2684
|
-
|
|
2685
|
-
|
|
2745
|
+
await refreshSttStatus();
|
|
2746
|
+
|
|
2747
|
+
/* ---------- olaylar ---------- */
|
|
2748
|
+
['piperSpeed', 'piperSil', 'piperNoise'].forEach((id) => {
|
|
2749
|
+
const el = $('#' + id);
|
|
2750
|
+
if (el) el.addEventListener('input', syncPiperLabels);
|
|
2751
|
+
});
|
|
2752
|
+
document.querySelectorAll('input[name="ttsEngine"]').forEach((r) => {
|
|
2753
|
+
r.addEventListener('change', () => {
|
|
2754
|
+
captureVoice();
|
|
2755
|
+
refreshSeg('ttsEngine');
|
|
2756
|
+
fillVoices();
|
|
2757
|
+
syncEngineUi();
|
|
2758
|
+
refreshPiperStatus();
|
|
2759
|
+
});
|
|
2760
|
+
});
|
|
2761
|
+
document.querySelectorAll('input[name="sttEngine"]').forEach((r) => {
|
|
2762
|
+
r.addEventListener('change', async () => {
|
|
2763
|
+
refreshSeg('sttEngine');
|
|
2764
|
+
const res = await beast.sttProviderSet(r.value).catch(() => null);
|
|
2765
|
+
if (res && res.ok) {
|
|
2766
|
+
await refreshSttStatus();
|
|
2767
|
+
toast('STT motoru: ' + sttName(res));
|
|
2768
|
+
} else {
|
|
2769
|
+
toast('STT motoru değiştirilemedi');
|
|
2770
|
+
}
|
|
2771
|
+
});
|
|
2772
|
+
});
|
|
2686
2773
|
$('#piperDlBtn').addEventListener('click', async () => {
|
|
2687
|
-
|
|
2774
|
+
captureVoice();
|
|
2688
2775
|
toast('Piper indiriliyor… (ilk seferde ~80MB)');
|
|
2689
2776
|
const poll = setInterval(refreshPiperStatus, 4000);
|
|
2690
|
-
const r = await beast.piperInstall(
|
|
2777
|
+
const r = await beast.piperInstall(piperVoice).catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
2691
2778
|
clearInterval(poll);
|
|
2692
2779
|
await refreshPiperStatus();
|
|
2693
2780
|
toast(r && r.ok ? 'Piper hazır ✓ — artık çevrimdışı seslendirme yapılır' : 'Piper indirilemedi: ' + ((r && r.error) || '?'));
|
|
2694
2781
|
});
|
|
2695
|
-
|
|
2696
|
-
/* STT durum satırı: yenile + şimdi indir (yüklenirken 3 sn'de bir güncellenir) */
|
|
2697
|
-
const refreshSttStatus = async () => {
|
|
2698
|
-
try {
|
|
2699
|
-
stt = await beast.sttStatus();
|
|
2700
|
-
const el = $('#sttStatusTxt');
|
|
2701
|
-
if (el && stt) el.textContent = stt.engineLabel + ' · ' + (stateText[stt.state] || stt.state) + (stt.mb ? ' · ' + stt.mb + ' MB' : '');
|
|
2702
|
-
return stt;
|
|
2703
|
-
} catch { return null; }
|
|
2704
|
-
};
|
|
2705
2782
|
$('#sttDlBtn').addEventListener('click', async () => {
|
|
2783
|
+
const s0 = await refreshSttStatus();
|
|
2784
|
+
if (s0 && s0.state === 'cloud') { toast('Bulut motoru seçili — indirme gerekmez'); return; }
|
|
2706
2785
|
await beast.sttPrefetchNow().catch(() => {});
|
|
2707
2786
|
toast('STT modeli indiriliyor/yükleniyor…');
|
|
2708
2787
|
const poll = setInterval(async () => {
|
|
2709
|
-
const
|
|
2710
|
-
if (
|
|
2788
|
+
const s2 = await refreshSttStatus();
|
|
2789
|
+
if (s2 && (s2.state === 'ready' || s2.state === 'cloud')) { clearInterval(poll); toast('STT hazır ✓'); }
|
|
2711
2790
|
}, 3000);
|
|
2712
2791
|
});
|
|
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
|
-
});
|
|
2723
2792
|
$('#ttsSave').addEventListener('click', async () => {
|
|
2793
|
+
captureVoice();
|
|
2724
2794
|
await beast.waSetTts({
|
|
2725
2795
|
enabled: $('#ttsOn').checked,
|
|
2726
|
-
engine:
|
|
2727
|
-
edgeVoice
|
|
2728
|
-
piperVoice
|
|
2796
|
+
engine: ttsEng(),
|
|
2797
|
+
edgeVoice,
|
|
2798
|
+
piperVoice,
|
|
2799
|
+
piperSpeed: Number($('#piperSpeed').value),
|
|
2800
|
+
piperSilence: Number($('#piperSil').value),
|
|
2801
|
+
piperNoise: Number($('#piperNoise').value),
|
|
2729
2802
|
chatAutoSpeak: $('#ttsChatAuto').checked,
|
|
2730
2803
|
baseUrl: $('#ttsUrl').value.trim(),
|
|
2731
2804
|
key: $('#ttsKey').value.trim(),
|
|
@@ -2740,7 +2813,7 @@ async function renderTtsPane() { const pane = $('#tab-tts');
|
|
|
2740
2813
|
});
|
|
2741
2814
|
$('#ttsTest').addEventListener('click', async () => {
|
|
2742
2815
|
/* motor + IPC + çalma zincirini uçtan uca dener — sorun neredeyse görünür */
|
|
2743
|
-
const eng =
|
|
2816
|
+
const eng = ttsEng();
|
|
2744
2817
|
const line = eng === 'piper'
|
|
2745
2818
|
? 'Merhaba kanka, Piper yerel TTS testi. Ben Beast.'
|
|
2746
2819
|
: eng === 'openai'
|
package/src/renderer/style.css
CHANGED
|
@@ -1741,6 +1741,63 @@ body.browser-open #settingsDialog { width: min(70vw, calc(100vw - var(--bw, 480p
|
|
|
1741
1741
|
.inp:focus { border-color: var(--accent); }
|
|
1742
1742
|
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 10px; }
|
|
1743
1743
|
|
|
1744
|
+
/* ---------------- 🎙 Sesli Yanıt (STT / TTS) ---------------- */
|
|
1745
|
+
.voice-card {
|
|
1746
|
+
border: 1px solid var(--border);
|
|
1747
|
+
border-radius: 12px;
|
|
1748
|
+
background: var(--panel);
|
|
1749
|
+
padding: 16px 18px;
|
|
1750
|
+
max-width: 640px;
|
|
1751
|
+
}
|
|
1752
|
+
.voice-card + .voice-card { margin-top: 14px; }
|
|
1753
|
+
.voice-head { display: flex; align-items: center; gap: 11px; }
|
|
1754
|
+
.voice-ico {
|
|
1755
|
+
width: 34px; height: 34px; flex: none;
|
|
1756
|
+
border-radius: 9px;
|
|
1757
|
+
display: flex; align-items: center; justify-content: center;
|
|
1758
|
+
font-size: 17px;
|
|
1759
|
+
background: var(--accent-dim);
|
|
1760
|
+
color: var(--accent);
|
|
1761
|
+
}
|
|
1762
|
+
.voice-title { font-weight: 700; font-size: 13.5px; }
|
|
1763
|
+
.voice-sub { font-size: 12px; color: var(--muted); margin-top: 1px; }
|
|
1764
|
+
.voice-seg { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 13px; }
|
|
1765
|
+
.voice-seg label {
|
|
1766
|
+
position: relative;
|
|
1767
|
+
border: 1px solid var(--border);
|
|
1768
|
+
border-radius: 9px;
|
|
1769
|
+
padding: 7px 13px;
|
|
1770
|
+
font-size: 12.5px;
|
|
1771
|
+
cursor: pointer;
|
|
1772
|
+
color: var(--muted);
|
|
1773
|
+
background: var(--panel2);
|
|
1774
|
+
user-select: none;
|
|
1775
|
+
transition: border-color .12s, color .12s, background .12s;
|
|
1776
|
+
}
|
|
1777
|
+
.voice-seg label:hover { border-color: var(--accent); color: var(--text); }
|
|
1778
|
+
.voice-seg input { position: absolute; opacity: 0; pointer-events: none; }
|
|
1779
|
+
.voice-seg label.on { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); font-weight: 700; }
|
|
1780
|
+
.voice-row { display: flex; align-items: center; gap: 12px; margin-top: 12px; flex-wrap: wrap; }
|
|
1781
|
+
.voice-row .inp { flex: 1; min-width: 220px; margin: 0; }
|
|
1782
|
+
.voice-status { display: flex; align-items: center; gap: 8px; margin-top: 12px; font-size: 12.5px; color: var(--muted); flex-wrap: wrap; }
|
|
1783
|
+
.voice-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex: none; }
|
|
1784
|
+
.voice-dot.ok { background: var(--ok); }
|
|
1785
|
+
.voice-dot.warn { background: #d9a441; }
|
|
1786
|
+
.voice-sliders { margin-top: 12px; border-top: 1px dashed var(--border); padding-top: 10px; }
|
|
1787
|
+
.voice-slider {
|
|
1788
|
+
display: grid;
|
|
1789
|
+
grid-template-columns: 132px 1fr 48px;
|
|
1790
|
+
align-items: center;
|
|
1791
|
+
gap: 10px;
|
|
1792
|
+
margin-top: 7px;
|
|
1793
|
+
font-size: 12.5px;
|
|
1794
|
+
color: var(--muted);
|
|
1795
|
+
}
|
|
1796
|
+
.voice-slider input[type=range] { width: 100%; accent-color: var(--accent); }
|
|
1797
|
+
.voice-slider b { text-align: right; color: var(--text); font-variant-numeric: tabular-nums; }
|
|
1798
|
+
.voice-actions { display: flex; gap: 8px; margin-top: 15px; }
|
|
1799
|
+
.voice-note { font-size: 12px; color: var(--muted); margin-top: 11px; line-height: 1.5; }
|
|
1800
|
+
|
|
1744
1801
|
/* ---------------- ☢ Fallout ---------------- */
|
|
1745
1802
|
.fo-toggles {
|
|
1746
1803
|
display: flex;
|