beast-agent 2.44.12 → 2.44.14
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 +53 -4
- package/src/renderer/i18n.js +18 -0
- package/src/renderer/renderer.js +257 -154
- 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;
|
|
@@ -7982,6 +7985,36 @@ function termSend(ev) {
|
|
|
7982
7985
|
if (win && !win.isDestroyed()) win.webContents.send('agent:event', ev);
|
|
7983
7986
|
}
|
|
7984
7987
|
|
|
7988
|
+
/* TERMİNAL ÇIKTI COALESCE: alt süreçten gelen data chunk'ları 80ms'de bir TEK
|
|
7989
|
+
'term-batch' IPC'sinde birleştirilir. Aksi halde saniyede yüzlerce IPC +
|
|
7990
|
+
renderer'da satır satır DOM/layout = "Yanıt vermiyor" donması. */
|
|
7991
|
+
const TERM_BATCH_MS = 80;
|
|
7992
|
+
const TERM_BATCH_BYTES = 48 * 1024;
|
|
7993
|
+
const termBatches = new Map(); // id -> { timer, bytes, events: [{stream,text}] }
|
|
7994
|
+
|
|
7995
|
+
function termQueue(id, stream, text) {
|
|
7996
|
+
const key = String(id || termShellId || '');
|
|
7997
|
+
let b = termBatches.get(key);
|
|
7998
|
+
if (!b) {
|
|
7999
|
+
b = { timer: null, bytes: 0, events: [] };
|
|
8000
|
+
termBatches.set(key, b);
|
|
8001
|
+
}
|
|
8002
|
+
b.events.push({ stream, chunk: String(text) });
|
|
8003
|
+
b.bytes += String(text).length;
|
|
8004
|
+
if (b.bytes >= TERM_BATCH_BYTES) return termFlush(key); // tampon taştı — hemen boşalt
|
|
8005
|
+
if (!b.timer) b.timer = setTimeout(() => termFlush(key), TERM_BATCH_MS);
|
|
8006
|
+
}
|
|
8007
|
+
|
|
8008
|
+
function termFlush(id) {
|
|
8009
|
+
const key = String(id || '');
|
|
8010
|
+
const b = termBatches.get(key);
|
|
8011
|
+
if (!b) return;
|
|
8012
|
+
if (b.timer) { clearTimeout(b.timer); b.timer = null; }
|
|
8013
|
+
termBatches.delete(key);
|
|
8014
|
+
if (!b.events.length) return;
|
|
8015
|
+
termSend({ type: 'term-batch', id: key, events: b.events });
|
|
8016
|
+
}
|
|
8017
|
+
|
|
7985
8018
|
/* Kalıcı kabuğu başlat: cmd /q /k — girdi pipe'ından satır satır okur,
|
|
7986
8019
|
prompt yazmaz. chcp 65001 → Türkçe yollar (Masaüstü vb.) doğru çözülür. */
|
|
7987
8020
|
function termShellSpawn() {
|
|
@@ -8006,14 +8039,15 @@ function termShellSpawn() {
|
|
|
8006
8039
|
if (termForwarded > TERM_FWD_CAP) {
|
|
8007
8040
|
if (!termCapNotified) {
|
|
8008
8041
|
termCapNotified = true;
|
|
8042
|
+
termFlush(termShellId);
|
|
8009
8043
|
termSend({ type: 'term-out', id: termShellId, stream: 'out', chunk: '\n[beast] çıktı çok büyük — iletim durduruldu (komut sürüyor)\n' });
|
|
8010
8044
|
}
|
|
8011
8045
|
return;
|
|
8012
8046
|
}
|
|
8013
|
-
|
|
8047
|
+
termQueue(termShellId, 'out', String(d));
|
|
8014
8048
|
});
|
|
8015
8049
|
child.stderr.on('data', (d) => {
|
|
8016
|
-
|
|
8050
|
+
termQueue(termShellId, 'err', String(d));
|
|
8017
8051
|
});
|
|
8018
8052
|
child.on('exit', () => {
|
|
8019
8053
|
if (termChild === child) termChild = null;
|
|
@@ -8038,6 +8072,7 @@ ipcMain.handle('terminal:run', (_e, payload) => {
|
|
|
8038
8072
|
if (!termChild || !termChild.stdin.writable) termShellSpawn();
|
|
8039
8073
|
if (!termChild) return { ok: false, error: 'kabuk başlatılamadı' };
|
|
8040
8074
|
const id = 't' + Date.now().toString(36) + ++termShellSeq;
|
|
8075
|
+
termFlush(termShellId); // önceki komuttan kalan çıktı sırayı bozmasın
|
|
8041
8076
|
termShellId = id;
|
|
8042
8077
|
termForwarded = 0;
|
|
8043
8078
|
termCapNotified = false;
|
|
@@ -8070,12 +8105,14 @@ ipcMain.handle('terminal:run', (_e, payload) => {
|
|
|
8070
8105
|
}
|
|
8071
8106
|
child.stdout.setEncoding('utf8');
|
|
8072
8107
|
child.stderr.setEncoding('utf8');
|
|
8073
|
-
child.stdout.on('data', (d) =>
|
|
8074
|
-
child.stderr.on('data', (d) =>
|
|
8108
|
+
child.stdout.on('data', (d) => termQueue(id, 'out', String(d)));
|
|
8109
|
+
child.stderr.on('data', (d) => termQueue(id, 'err', String(d)));
|
|
8075
8110
|
child.on('error', (err) => {
|
|
8111
|
+
termFlush(id);
|
|
8076
8112
|
termSend({ type: 'term-end', id, code: -1, error: String((err && err.message) || err) });
|
|
8077
8113
|
});
|
|
8078
8114
|
child.on('close', (code) => {
|
|
8115
|
+
termFlush(id);
|
|
8079
8116
|
termSend({ type: 'term-end', id, code: code == null ? -1 : code });
|
|
8080
8117
|
});
|
|
8081
8118
|
return { ok: true, id };
|
|
@@ -8089,6 +8126,7 @@ ipcMain.handle('terminal:stop', () => {
|
|
|
8089
8126
|
try { termChild.kill(); } catch {}
|
|
8090
8127
|
termChild = null;
|
|
8091
8128
|
}
|
|
8129
|
+
termFlush(termShellId);
|
|
8092
8130
|
termSend({ type: 'term-end', id: termShellId, code: 130, error: 'komut durduruldu — kalıcı CMD yeniden hazır' });
|
|
8093
8131
|
termShellId = null;
|
|
8094
8132
|
return { ok: true };
|
|
@@ -12879,6 +12917,13 @@ ipcMain.handle('wa:queue:get', () => mqueue.stats());
|
|
|
12879
12917
|
|
|
12880
12918
|
ipcMain.handle('wa:sessions', () => [...waChats.values()]);
|
|
12881
12919
|
|
|
12920
|
+
/* TTS sayısal ayar kelepçesi: geçersiz/boş değerde varsayılana döner */
|
|
12921
|
+
function clampTtsNum(v, lo, hi, def) {
|
|
12922
|
+
const n = Number(v);
|
|
12923
|
+
if (!Number.isFinite(n)) return def;
|
|
12924
|
+
return Math.max(lo, Math.min(hi, n));
|
|
12925
|
+
}
|
|
12926
|
+
|
|
12882
12927
|
ipcMain.handle('wa:tts:get', () => settings.waTts || {});
|
|
12883
12928
|
ipcMain.handle('wa:tts:set', (_e, cfg) => {
|
|
12884
12929
|
const eng = String((cfg && cfg.engine) || 'edge');
|
|
@@ -12889,6 +12934,10 @@ ipcMain.handle('wa:tts:set', (_e, cfg) => {
|
|
|
12889
12934
|
engine: ['edge', 'piper', 'openai'].includes(eng) ? eng : 'edge',
|
|
12890
12935
|
edgeVoice: String((cfg && cfg.edgeVoice) || 'tr-TR-AhmetNeural').trim(),
|
|
12891
12936
|
piperVoice: piper.VOICES[pv] ? pv : piper.DEFAULT_VOICE,
|
|
12937
|
+
/* Piper ince ayar: hız (0.6-1.6), cümle sonu duraklama (0-1 sn), ifade (0-1.2) */
|
|
12938
|
+
piperSpeed: clampTtsNum(cfg && cfg.piperSpeed, 0.6, 1.6, 1),
|
|
12939
|
+
piperSilence: clampTtsNum(cfg && cfg.piperSilence, 0, 1, 0.2),
|
|
12940
|
+
piperNoise: clampTtsNum(cfg && cfg.piperNoise, 0, 1.2, 0.667),
|
|
12892
12941
|
chatAutoSpeak: !!(cfg && cfg.chatAutoSpeak),
|
|
12893
12942
|
baseUrl: String((cfg && cfg.baseUrl) || '').trim(),
|
|
12894
12943
|
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'
|
|
@@ -6114,29 +6187,13 @@ function onEvent(ev) {
|
|
|
6114
6187
|
if (!busy) break;
|
|
6115
6188
|
setStatus(ev.status === 'idle' ? (busy ? 'düşünüyor…' : '') : ev.status === 'thinking' ? 'düşünüyor…' : ev.status + '…');
|
|
6116
6189
|
break;
|
|
6117
|
-
case 'term-out':
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
const parts = termPendBuf.split(/\r?\n/);
|
|
6124
|
-
termPendBuf = parts.pop(); /* son yarım satır bekler */
|
|
6125
|
-
for (const line of parts) {
|
|
6126
|
-
const m = /__BEAST_EOF__(.*)__BEAST_EOF__/.exec(line.trim());
|
|
6127
|
-
if (m) {
|
|
6128
|
-
const cwd2 = m[1].trim();
|
|
6129
|
-
if (cwd2) {
|
|
6130
|
-
els.termCwd.textContent = cwd2;
|
|
6131
|
-
els.termCwd.title = cwd2;
|
|
6132
|
-
}
|
|
6133
|
-
termCmdDone({ code: 0, silent: true });
|
|
6134
|
-
continue;
|
|
6135
|
-
}
|
|
6136
|
-
if (line.trim()) termLine('t-out', line, false);
|
|
6137
|
-
}
|
|
6190
|
+
case 'term-out':
|
|
6191
|
+
termHandleOut(ev);
|
|
6192
|
+
break;
|
|
6193
|
+
case 'term-batch':
|
|
6194
|
+
/* main 80ms'de bir birleştirilmiş çıktı paketi gönderir (IPC/satır yükü azalır) */
|
|
6195
|
+
for (const sub of (ev.events || [])) termHandleOut({ ...sub, id: ev.id });
|
|
6138
6196
|
break;
|
|
6139
|
-
}
|
|
6140
6197
|
case 'term-end':
|
|
6141
6198
|
termCmdDone(ev);
|
|
6142
6199
|
break;
|
|
@@ -6243,6 +6300,7 @@ function termSetHeight(h) {
|
|
|
6243
6300
|
function termSetOpen(v) {
|
|
6244
6301
|
termOpen = !!v;
|
|
6245
6302
|
els.termPanel.hidden = !v;
|
|
6303
|
+
if (v) termFlushLines(); // panel kapalıyken biriken satırlar şimdi basılır
|
|
6246
6304
|
els.termResize.hidden = !v || ideModeOn(); /* IDE'de terminal bölme içi — boyutlandırma yok */
|
|
6247
6305
|
document.body.classList.toggle('term-open', v);
|
|
6248
6306
|
termSetShell(termShell);
|
|
@@ -6271,16 +6329,38 @@ function termScroll(force) {
|
|
|
6271
6329
|
if (force || near) b.scrollTop = b.scrollHeight;
|
|
6272
6330
|
}
|
|
6273
6331
|
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6332
|
+
/* satır KUYRUĞU: komut çıktısı hızlı akarken satır satır DOM + scroll/layout
|
|
6333
|
+
renderer'ı kilitliyordu ("Yanıt vermiyor"). Satırlar tampona girer, 100ms'de
|
|
6334
|
+
bir TEK fragment ile DOM'a basılır; panel kapalıysa DOM'a hiç dokunulmaz. */
|
|
6335
|
+
let termLineBuf = [];
|
|
6336
|
+
let termLineTimer = null;
|
|
6337
|
+
|
|
6338
|
+
function termFlushLines() {
|
|
6339
|
+
if (termLineTimer) { clearTimeout(termLineTimer); termLineTimer = null; }
|
|
6340
|
+
if (!termLineBuf.length || !els.termOut) return;
|
|
6341
|
+
const items = termLineBuf.splice(0);
|
|
6342
|
+
const frag = document.createDocumentFragment();
|
|
6343
|
+
for (const it of items) {
|
|
6344
|
+
const el = document.createElement('div');
|
|
6345
|
+
el.className = 't-line ' + (it.cls || '');
|
|
6346
|
+
const ts = it.time ? '<span class="t-time">' + new Date().toTimeString().slice(0, 8) + '</span> ' : '';
|
|
6347
|
+
el.innerHTML = ts + escapeHtml(String(it.text ?? ''));
|
|
6348
|
+
frag.appendChild(el);
|
|
6349
|
+
}
|
|
6350
|
+
els.termOut.appendChild(frag);
|
|
6280
6351
|
while (els.termOut.childElementCount > TERM_MAX_LINES) els.termOut.removeChild(els.termOut.firstChild);
|
|
6281
6352
|
termScroll();
|
|
6282
6353
|
}
|
|
6283
6354
|
|
|
6355
|
+
function termLine(cls, text, time = true) {
|
|
6356
|
+
termLineBuf.push({ cls, text, time });
|
|
6357
|
+
const cap = TERM_MAX_LINES * 2;
|
|
6358
|
+
if (termLineBuf.length > cap) termLineBuf.splice(0, termLineBuf.length - cap);
|
|
6359
|
+
if (!termOpen) return; /* panel kapalı — açılınca termFlushLines ile basılır */
|
|
6360
|
+
if (termLineBuf.length >= 300) termFlushLines();
|
|
6361
|
+
else if (!termLineTimer) termLineTimer = setTimeout(termFlushLines, 100);
|
|
6362
|
+
}
|
|
6363
|
+
|
|
6284
6364
|
function termBanner(cwd) {
|
|
6285
6365
|
if (termBannerDone) return;
|
|
6286
6366
|
termBannerDone = true;
|
|
@@ -6319,6 +6399,29 @@ function termAgentEvent(ev) {
|
|
|
6319
6399
|
}
|
|
6320
6400
|
}
|
|
6321
6401
|
|
|
6402
|
+
/* KALICI CMD çıktısı: her komutun sonunda marker satırı gelir —
|
|
6403
|
+
__BEAST_EOF__<yol>__BEAST_EOF__ → yazdırılmaz, cwd güncellenir, kilit açılır */
|
|
6404
|
+
function termHandleOut(ev) {
|
|
6405
|
+
const chunk = String(ev.chunk || '');
|
|
6406
|
+
if (ev.stream === 'err') { termLine('t-err', chunk, false); return; }
|
|
6407
|
+
termPendBuf += chunk;
|
|
6408
|
+
const parts = termPendBuf.split(/\r?\n/);
|
|
6409
|
+
termPendBuf = parts.pop(); /* son yarım satır bekler */
|
|
6410
|
+
for (const line of parts) {
|
|
6411
|
+
const m = /__BEAST_EOF__(.*)__BEAST_EOF__/.exec(line.trim());
|
|
6412
|
+
if (m) {
|
|
6413
|
+
const cwd2 = m[1].trim();
|
|
6414
|
+
if (cwd2) {
|
|
6415
|
+
els.termCwd.textContent = cwd2;
|
|
6416
|
+
els.termCwd.title = cwd2;
|
|
6417
|
+
}
|
|
6418
|
+
termCmdDone({ code: 0, silent: true });
|
|
6419
|
+
continue;
|
|
6420
|
+
}
|
|
6421
|
+
if (line.trim()) termLine('t-out', line, false);
|
|
6422
|
+
}
|
|
6423
|
+
}
|
|
6424
|
+
|
|
6322
6425
|
function termCmdDone(ev) {
|
|
6323
6426
|
termRunning = false;
|
|
6324
6427
|
els.termStop.hidden = true;
|
|
@@ -7639,7 +7742,7 @@ async function init() {
|
|
|
7639
7742
|
try { termSetHeight(parseInt(localStorage.getItem('beast.termH')) || 180); } catch {}
|
|
7640
7743
|
if (els.termCBtn) els.termCBtn.addEventListener('click', () => termToggle('cmd'));
|
|
7641
7744
|
if (els.termClose) els.termClose.addEventListener('click', () => termSetOpen(false));
|
|
7642
|
-
if (els.termClear) els.termClear.addEventListener('click', () => { els.termOut.innerHTML = ''; });
|
|
7745
|
+
if (els.termClear) els.termClear.addEventListener('click', () => { els.termOut.innerHTML = ''; termLineBuf = []; });
|
|
7643
7746
|
if (els.termStop) els.termStop.addEventListener('click', () => beast.terminalStop().catch(() => {}));
|
|
7644
7747
|
if (els.termInput) els.termInput.addEventListener('keydown', (e) => {
|
|
7645
7748
|
if (e.key === 'Enter') {
|
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;
|