beast-agent 2.2.1 → 2.3.2
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 +7 -1
- package/src/agent/edgetts.js +201 -0
- package/src/agent/engine.js +93 -1
- package/src/agent/nightref.js +404 -0
- package/src/main.js +937 -57
- package/src/preload.js +9 -2
- package/src/renderer/handsfree.js +633 -0
- package/src/renderer/i18n.js +32 -6
- package/src/renderer/index.html +21 -7
- package/src/renderer/renderer.js +506 -81
- package/src/renderer/style.css +114 -24
package/src/renderer/renderer.js
CHANGED
|
@@ -59,6 +59,8 @@ const els = {
|
|
|
59
59
|
bbClose: $('#bbClose'),
|
|
60
60
|
bbResize: $('#bbResize'),
|
|
61
61
|
bbPhone: $('#bbPhone'),
|
|
62
|
+
bbMobile: $('#bbMobile'),
|
|
63
|
+
bbDevice: $('#bbDevice'),
|
|
62
64
|
termCBtn: $('#termCBtn'),
|
|
63
65
|
termPanel: $('#termPanel'),
|
|
64
66
|
termCwd: $('#termCwd'),
|
|
@@ -137,6 +139,7 @@ const els = {
|
|
|
137
139
|
setClose: $('#setClose'),
|
|
138
140
|
setVersion: $('#setVersion'),
|
|
139
141
|
attachBtn: $('#attachBtn'),
|
|
142
|
+
ttsBtn: $('#ttsBtn'),
|
|
140
143
|
micBtn: $('#micBtn'),
|
|
141
144
|
fileInput: $('#fileInput'),
|
|
142
145
|
fileTasks: $('#fileTasks'),
|
|
@@ -1891,38 +1894,329 @@ async function renderSkillsPane() {
|
|
|
1891
1894
|
|
|
1892
1895
|
/* ---------------- sesli yanıt (TTS) ---------------- */
|
|
1893
1896
|
|
|
1897
|
+
/* ---------------- CHAT TTS: ajan yazılarını otomatik seslendirme ----------------
|
|
1898
|
+
Ayarlar → Sesli Yanıt'ta "chat'te otomatik seslendir" açıksa her tur sonunda
|
|
1899
|
+
ajanın son yazısı Edge TTS (veya seçili motor) ile okunur. */
|
|
1900
|
+
let ttsCfgCache = null;
|
|
1901
|
+
let ttsAudioEl = null;
|
|
1902
|
+
let ttsLastSpoken = '';
|
|
1903
|
+
let chatTtsOn = false; // hoparlör düğmesi: chat'te otomatik seslendirme
|
|
1904
|
+
|
|
1905
|
+
async function ttsCfg() {
|
|
1906
|
+
if (!ttsCfgCache) {
|
|
1907
|
+
try { ttsCfgCache = await beast.waGetTts(); } catch { ttsCfgCache = {}; }
|
|
1908
|
+
}
|
|
1909
|
+
return ttsCfgCache || {};
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/* açılışta TTS tercihini yükle + düğme durumunu senkronla */
|
|
1913
|
+
(async () => {
|
|
1914
|
+
const cfg = await ttsCfg();
|
|
1915
|
+
chatTtsOn = !!cfg.chatAutoSpeak;
|
|
1916
|
+
if (els.ttsBtn) els.ttsBtn.classList.toggle('on', chatTtsOn);
|
|
1917
|
+
})();
|
|
1918
|
+
if (els.ttsBtn) {
|
|
1919
|
+
els.ttsBtn.addEventListener('click', async () => {
|
|
1920
|
+
chatTtsOn = !chatTtsOn;
|
|
1921
|
+
els.ttsBtn.classList.toggle('on', chatTtsOn);
|
|
1922
|
+
/* KAPATMA = ANINDA SUS: çalan cümle durur + kuyruktaki tüm cümleler atılır */
|
|
1923
|
+
if (!chatTtsOn) ttsQueueReset();
|
|
1924
|
+
const cfg = await ttsCfg();
|
|
1925
|
+
ttsCfgCache = { ...cfg, chatAutoSpeak: chatTtsOn };
|
|
1926
|
+
await beast.waSetTts(ttsCfgCache).catch(() => {});
|
|
1927
|
+
if (!cfg.enabled) {
|
|
1928
|
+
toast(chatTtsOn ? 'TTS motoru kapalı — Ayarlar → Sesli Yanıttan etkinleştir' : 'Otomatik seslendirme kapalı');
|
|
1929
|
+
} else {
|
|
1930
|
+
toast(chatTtsOn ? 'Otomatik seslendirme AÇIK' : 'Otomatik seslendirme kapalı');
|
|
1931
|
+
}
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
async function speakText(text) {
|
|
1936
|
+
try {
|
|
1937
|
+
const cfg = await ttsCfg();
|
|
1938
|
+
/* chatTtsOn tek doğruluk kaynağı: hoparlör düğmesi + ayar paneli + boot yükleme
|
|
1939
|
+
üçü de senkron tutar. Yalnız chat modunda; terminal/BC/Studio asla okunmaz. */
|
|
1940
|
+
if (!cfg.enabled || !chatTtsOn || ideModeOn() || studioModeOn()) return;
|
|
1941
|
+
const clean = speechReadyText(text);
|
|
1942
|
+
if (!clean) return;
|
|
1943
|
+
if (clean === ttsLastSpoken) return; // aynı yazıyı iki kez okuma
|
|
1944
|
+
const r = await beast.ttsSpeak(clean.slice(0, 4000));
|
|
1945
|
+
if (!(r && r.ok && r.audioB64)) {
|
|
1946
|
+
toast('TTS: ' + ((r && r.error) || 'seslendirilemedi'));
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
ttsLastSpoken = clean;
|
|
1950
|
+
/* STT YANKI KÖPRÜSÜ: mikrofon, seslendirme bitene kadar tetiklenmez */
|
|
1951
|
+
const st = window.BeastHandsFree && window.BeastHandsFree.ttsState;
|
|
1952
|
+
if (st) { st.lastText = clean; st.playing = true; }
|
|
1953
|
+
await playTtsB64(r);
|
|
1954
|
+
} catch {}
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
/* base64 mp3 → Blob URL → Audio. data: URL CSP'de engellendiği için blob kullanılır.
|
|
1958
|
+
Promise, çalma BİTTİĞİNDE çözülür (cümle kuyruğu sırayı böyle kurar);
|
|
1959
|
+
kesinti (pause/barge) ttsPlayResolve üzerinden anında çözer. */
|
|
1960
|
+
async function playTtsB64(r) {
|
|
1961
|
+
const bin = atob(r.audioB64);
|
|
1962
|
+
const bytes = new Uint8Array(bin.length);
|
|
1963
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1964
|
+
const blob = new Blob([bytes], { type: r.mime || 'audio/mpeg' });
|
|
1965
|
+
const url = URL.createObjectURL(blob);
|
|
1966
|
+
if (ttsAudioEl) { try { ttsAudioEl.pause(); } catch {} }
|
|
1967
|
+
return new Promise((resolve) => {
|
|
1968
|
+
let settled = false;
|
|
1969
|
+
const done = () => {
|
|
1970
|
+
if (settled) return;
|
|
1971
|
+
settled = true;
|
|
1972
|
+
ttsPlayResolve = null;
|
|
1973
|
+
try { URL.revokeObjectURL(url); } catch {}
|
|
1974
|
+
const st2 = window.BeastHandsFree && window.BeastHandsFree.ttsState;
|
|
1975
|
+
if (st2) st2.lastEndedAt = Date.now(); // yankı penceresi için
|
|
1976
|
+
setTimeout(() => {
|
|
1977
|
+
const st = window.BeastHandsFree && window.BeastHandsFree.ttsState;
|
|
1978
|
+
if (st) st.playing = false;
|
|
1979
|
+
}, 300);
|
|
1980
|
+
resolve();
|
|
1981
|
+
};
|
|
1982
|
+
ttsPlayResolve = done;
|
|
1983
|
+
ttsAudioEl = new Audio(url);
|
|
1984
|
+
ttsAudioEl.onended = done;
|
|
1985
|
+
ttsAudioEl.onerror = done;
|
|
1986
|
+
const st0 = window.BeastHandsFree && window.BeastHandsFree.ttsState;
|
|
1987
|
+
if (st0) st0.playing = true; // ses başlıyor — mikrofon adaptif zemin moduna geçer
|
|
1988
|
+
ttsAudioEl.play().catch((e) => {
|
|
1989
|
+
const st = window.BeastHandsFree && window.BeastHandsFree.ttsState;
|
|
1990
|
+
if (st) st.playing = false;
|
|
1991
|
+
toast('TTS çalınamadı: ' + String((e && e.message) || e));
|
|
1992
|
+
done();
|
|
1993
|
+
});
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
/* ---------------- CÜMLE CÜMLE STREAMING TTS ----------------
|
|
1998
|
+
Ajan yazarken tamamlanan her cümle kuyruğa girer ve SIRAYLA okunur —
|
|
1999
|
+
cevabın bitmesi beklenmez. Kesinti (barge/stop) jenerasyonu artırır:
|
|
2000
|
+
eski cümleler anında çöker, yeni cevap temiz başlar. */
|
|
2001
|
+
let ttsQList = []; // seslendirilecek cümleler (sıra)
|
|
2002
|
+
let ttsReady = []; // sentezlenmiş: { text, r } — sırayla çalınır
|
|
2003
|
+
let ttsSynthBusy = false; // tek sentez biriminde PREFETCH: cümle 1 çalarken 2 sentezlenir
|
|
2004
|
+
let ttsQBusy = false; // şu an bir cümle ÇALIYOR mu
|
|
2005
|
+
let ttsQGen = 0;
|
|
2006
|
+
let ttsPending = '';
|
|
2007
|
+
let ttsPlayResolve = null;
|
|
2008
|
+
|
|
2009
|
+
function ttsAutoOn() {
|
|
2010
|
+
return !!chatTtsOn && !ideModeOn() && !studioModeOn();
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
/* openclaw tts-payload politikası: kısa metin okunmaz; kod-ağır metnin yerine
|
|
2014
|
+
"ekranda bıraktım" denir — kod okumak anlamsız ve kafa karıştırır */
|
|
2015
|
+
function speechReadyText(t) {
|
|
2016
|
+
const s = String(t || '').replace(/\s+/g, ' ').trim();
|
|
2017
|
+
if (s.length < 10) return '';
|
|
2018
|
+
const codeChars = (s.match(/[{}();=<>[\]\\]/g) || []).length;
|
|
2019
|
+
if (/```/.test(s) || codeChars / s.length >= 0.3) return 'Kod bloklarını ekranda bıraktım.';
|
|
2020
|
+
return s;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
function ttsQueueReset() {
|
|
2024
|
+
ttsQList = [];
|
|
2025
|
+
ttsReady = [];
|
|
2026
|
+
ttsPending = '';
|
|
2027
|
+
ttsQGen++;
|
|
2028
|
+
ttsSynthBusy = false; // in-flight sentez sonuçları gen kontrolüyle atılır
|
|
2029
|
+
ttsQBusy = false;
|
|
2030
|
+
try { if (ttsAudioEl) ttsAudioEl.pause(); } catch {}
|
|
2031
|
+
const st = window.BeastHandsFree && window.BeastHandsFree.ttsState;
|
|
2032
|
+
if (st) st.playing = false;
|
|
2033
|
+
if (ttsPlayResolve) { const r = ttsPlayResolve; ttsPlayResolve = null; r(); }
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
function ttsEnqueueSentence(sentence) {
|
|
2037
|
+
ttsQList.push(sentence);
|
|
2038
|
+
ttsKick();
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
/* PREFETCH: çalan cümlenin süresi boyunca SIRADAKİ cümle sentezlenir →
|
|
2042
|
+
cümleler arasında boşluk kalmaz (Edge istek gecikmesi gizlenir) */
|
|
2043
|
+
function ttsKick() {
|
|
2044
|
+
const myGen = ttsQGen;
|
|
2045
|
+
if (ttsSynthBusy) { ttsPump(); return; }
|
|
2046
|
+
if (!ttsQList.length) { ttsPump(); return; }
|
|
2047
|
+
const sentence = ttsQList.shift();
|
|
2048
|
+
ttsSynthBusy = true;
|
|
2049
|
+
beast.ttsSpeak(sentence.slice(0, 800))
|
|
2050
|
+
.then((r) => {
|
|
2051
|
+
ttsSynthBusy = false;
|
|
2052
|
+
if (myGen !== ttsQGen) return;
|
|
2053
|
+
if (r && r.ok && r.audioB64) ttsReady.push({ text: sentence, r });
|
|
2054
|
+
ttsKick();
|
|
2055
|
+
ttsPump();
|
|
2056
|
+
})
|
|
2057
|
+
.catch(() => {
|
|
2058
|
+
ttsSynthBusy = false;
|
|
2059
|
+
if (myGen !== ttsQGen) return;
|
|
2060
|
+
ttsKick();
|
|
2061
|
+
ttsPump();
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
async function ttsPump() {
|
|
2066
|
+
if (ttsQBusy) return;
|
|
2067
|
+
const myGen = ttsQGen;
|
|
2068
|
+
while (ttsReady.length) {
|
|
2069
|
+
if (myGen !== ttsQGen) return;
|
|
2070
|
+
const item = ttsReady.shift();
|
|
2071
|
+
ttsQBusy = true;
|
|
2072
|
+
try {
|
|
2073
|
+
ttsLastSpoken = item.text;
|
|
2074
|
+
const st = window.BeastHandsFree && window.BeastHandsFree.ttsState;
|
|
2075
|
+
if (st) { st.lastText = item.text; st.playing = true; }
|
|
2076
|
+
await playTtsB64(item.r);
|
|
2077
|
+
} catch {}
|
|
2078
|
+
ttsQBusy = false;
|
|
2079
|
+
if (myGen !== ttsQGen) return;
|
|
2080
|
+
ttsKick(); // çalma bitince sıradaki sentezi hemen başlat
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
function ttsFeedDelta(delta) {
|
|
2085
|
+
if (!ttsAutoOn()) return;
|
|
2086
|
+
ttsPending += String(delta || '');
|
|
2087
|
+
const parts = ttsPending.split(/(?<=[.!?…])\s+|\n+/);
|
|
2088
|
+
if (parts.length <= 1) return;
|
|
2089
|
+
const done = parts.slice(0, -1).map((s) => speechReadyText(s)).filter(Boolean);
|
|
2090
|
+
ttsPending = parts[parts.length - 1] || '';
|
|
2091
|
+
for (const s of done) ttsEnqueueSentence(s);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
function ttsFlushTail() {
|
|
2095
|
+
const rest = speechReadyText(ttsPending);
|
|
2096
|
+
ttsPending = '';
|
|
2097
|
+
if (rest) ttsEnqueueSentence(rest);
|
|
2098
|
+
else ttsKick();
|
|
2099
|
+
}
|
|
1894
2100
|
async function renderTtsPane() {
|
|
1895
2101
|
const pane = $('#tab-tts');
|
|
1896
2102
|
if (!pane) return;
|
|
2103
|
+
/* STT DURUMU: model indirildi mi / iniyor mu / hangi motor */
|
|
2104
|
+
let stt = null;
|
|
2105
|
+
try { stt = await beast.sttStatus(); } catch {}
|
|
2106
|
+
const stateText = {
|
|
2107
|
+
ready: 'HAZIR — model yüklü',
|
|
2108
|
+
loading: 'İNİYOR / yükleniyor…',
|
|
2109
|
+
downloaded: 'indirildi — ilk kullanımda yüklenir',
|
|
2110
|
+
partial: 'kısmen indi — devam edecek',
|
|
2111
|
+
missing: 'henüz indirilmedi',
|
|
2112
|
+
cloud: 'bulut motoru — indirme gerekmez',
|
|
2113
|
+
};
|
|
2114
|
+
const sttLine = stt
|
|
2115
|
+
? stt.engineLabel + ' · ' + (stateText[stt.state] || stt.state) + (stt.mb ? ' · ' + stt.mb + ' MB' : '')
|
|
2116
|
+
: (window.beast ? 'bilinmiyor' : '');
|
|
2117
|
+
const edgeOpts = [
|
|
2118
|
+
['tr-TR-AhmetNeural', 'Ahmet — Türkçe (erkek)'],
|
|
2119
|
+
['tr-TR-EmelNeural', 'Emel — Türkçe (kadın)'],
|
|
2120
|
+
['en-US-GuyNeural', 'Guy — English (male)'],
|
|
2121
|
+
['en-US-AriaNeural', 'Aria — English (female)'],
|
|
2122
|
+
['de-DE-KatjaNeural', 'Katja — Deutsch'],
|
|
2123
|
+
['ar-SA-HamedNeural', 'Hamed — العربية'],
|
|
2124
|
+
]
|
|
2125
|
+
.map(([v, n]) => `<option value="${v}">${n}</option>`)
|
|
2126
|
+
.join('');
|
|
1897
2127
|
pane.innerHTML =
|
|
1898
2128
|
'<h2>' + _t('tts_h2') + '</h2><div class="sub">' + _t('tts_sub') + '</div>' +
|
|
2129
|
+
'<div class="sub" style="margin:10px 0 4px;font-weight:700;color:var(--accent)">STT (Ses → Yazı)</div>' +
|
|
2130
|
+
`<div class="sub" id="sttStatusTxt" style="margin-bottom:8px">${sttLine}</div>` +
|
|
2131
|
+
`<button id="sttDlBtn" class="btn ghost" style="margin-bottom:14px">${_t('stt_download_now')}</button>` +
|
|
2132
|
+
'<div class="sub" style="margin:10px 0 4px;font-weight:700;color:var(--accent)">TTS (Yazı → Ses)</div>' +
|
|
1899
2133
|
`<div class="form-grid" style="grid-template-columns:auto 1fr 1fr;align-items:center;margin-top:10px">
|
|
1900
2134
|
<label class="lock-row"><input type="checkbox" id="ttsOn" /><span>${_t('tts_active')}</span></label>
|
|
1901
|
-
<
|
|
1902
|
-
<
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2135
|
+
<label style="grid-column:1">${_t('tts_engine')}</label>
|
|
2136
|
+
<select id="ttsEngine" class="inp" style="grid-column:2/4">
|
|
2137
|
+
<option value="edge">Edge TTS — yerel & ücretsiz (Ahmet/Emel…)</option>
|
|
2138
|
+
<option value="openai">OpenAI-uyumlu API (tts-1, ses: alloy…)</option>
|
|
2139
|
+
</select>
|
|
2140
|
+
<label style="grid-column:1">${_t('tts_edge_voice')}</label>
|
|
2141
|
+
<select id="ttsEdgeVoice" class="inp" style="grid-column:2/4">${edgeOpts}</select>
|
|
2142
|
+
<label class="lock-row" style="grid-column:1/4"><input type="checkbox" id="ttsChatAuto" /><span>${_t('tts_chat_auto')}</span></label>
|
|
2143
|
+
<span id="ttsOpenaiWrap" style="display:contents">
|
|
2144
|
+
<input id="ttsUrl" class="inp" placeholder="https://api.openai.com/v1" autocomplete="off" />
|
|
2145
|
+
<input id="ttsKey" class="inp" type="password" placeholder="API Key" autocomplete="off" />
|
|
2146
|
+
<input id="ttsModel" class="inp" placeholder="tts-1" autocomplete="off" />
|
|
2147
|
+
<input id="ttsVoice" class="inp" placeholder="ses: alloy" autocomplete="off" />
|
|
2148
|
+
</span>
|
|
2149
|
+
<button id="ttsSave" class="btn ghost" style="grid-column:1">${_t('tts_save')}</button>
|
|
2150
|
+
<button id="ttsTest" class="btn ghost" style="grid-column:2;justify-self:start">${_t('tts_test')}</button>
|
|
1906
2151
|
</div>` +
|
|
1907
2152
|
'<div class="sub" style="margin-top:8px">' + _t('tts_note') + '</div>';
|
|
1908
2153
|
|
|
2154
|
+
const syncEngineUi = () => {
|
|
2155
|
+
const isEdge = $('#ttsEngine').value === 'edge';
|
|
2156
|
+
$('#ttsEdgeVoice').disabled = !isEdge;
|
|
2157
|
+
$('#ttsOpenaiWrap').style.opacity = isEdge ? '0.35' : '1';
|
|
2158
|
+
$('#ttsOpenaiWrap').querySelectorAll('input').forEach((i) => (i.disabled = isEdge));
|
|
2159
|
+
};
|
|
2160
|
+
|
|
1909
2161
|
try {
|
|
1910
2162
|
const tts = await beast.waGetTts();
|
|
1911
2163
|
$('#ttsOn').checked = !!tts.enabled;
|
|
2164
|
+
$('#ttsEngine').value = tts.engine === 'openai' ? 'openai' : 'edge';
|
|
2165
|
+
$('#ttsEdgeVoice').value = tts.edgeVoice || 'tr-TR-AhmetNeural';
|
|
2166
|
+
$('#ttsChatAuto').checked = !!tts.chatAutoSpeak;
|
|
1912
2167
|
$('#ttsUrl').value = tts.baseUrl || '';
|
|
1913
2168
|
$('#ttsKey').value = tts.key || '';
|
|
1914
2169
|
$('#ttsModel').value = tts.model || 'tts-1';
|
|
1915
2170
|
$('#ttsVoice').value = tts.voice || 'alloy';
|
|
1916
2171
|
} catch {}
|
|
2172
|
+
syncEngineUi();
|
|
2173
|
+
$('#ttsEngine').addEventListener('change', syncEngineUi);
|
|
2174
|
+
|
|
2175
|
+
/* STT durum satırı: yenile + şimdi indir (yüklenirken 3 sn'de bir güncellenir) */
|
|
2176
|
+
const refreshSttStatus = async () => {
|
|
2177
|
+
try {
|
|
2178
|
+
stt = await beast.sttStatus();
|
|
2179
|
+
const el = $('#sttStatusTxt');
|
|
2180
|
+
if (el && stt) el.textContent = stt.engineLabel + ' · ' + (stateText[stt.state] || stt.state) + (stt.mb ? ' · ' + stt.mb + ' MB' : '');
|
|
2181
|
+
return stt;
|
|
2182
|
+
} catch { return null; }
|
|
2183
|
+
};
|
|
2184
|
+
$('#sttDlBtn').addEventListener('click', async () => {
|
|
2185
|
+
await beast.sttPrefetchNow().catch(() => {});
|
|
2186
|
+
toast('STT modeli indiriliyor/yükleniyor…');
|
|
2187
|
+
const poll = setInterval(async () => {
|
|
2188
|
+
const s = await refreshSttStatus();
|
|
2189
|
+
if (s && (s.state === 'ready' || s.state === 'cloud')) { clearInterval(poll); toast('STT hazır'); }
|
|
2190
|
+
}, 3000);
|
|
2191
|
+
});
|
|
1917
2192
|
$('#ttsSave').addEventListener('click', async () => {
|
|
1918
2193
|
await beast.waSetTts({
|
|
1919
2194
|
enabled: $('#ttsOn').checked,
|
|
2195
|
+
engine: $('#ttsEngine').value,
|
|
2196
|
+
edgeVoice: $('#ttsEdgeVoice').value,
|
|
2197
|
+
chatAutoSpeak: $('#ttsChatAuto').checked,
|
|
1920
2198
|
baseUrl: $('#ttsUrl').value.trim(),
|
|
1921
2199
|
key: $('#ttsKey').value.trim(),
|
|
1922
2200
|
model: $('#ttsModel').value.trim(),
|
|
1923
2201
|
voice: $('#ttsVoice').value.trim(),
|
|
1924
2202
|
});
|
|
1925
|
-
|
|
2203
|
+
ttsCfgCache = null; // ayar önbelleğini tazele
|
|
2204
|
+
/* hoparlör düğmesiyle TEK doğruluk kaynağı senkronu — asıl seslendirmeme bug'ı */
|
|
2205
|
+
chatTtsOn = $('#ttsChatAuto').checked;
|
|
2206
|
+
if (els.ttsBtn) els.ttsBtn.classList.toggle('on', chatTtsOn);
|
|
2207
|
+
toast($('#ttsOn').checked ? 'TTS açık — cevaplar sesli' : 'TTS kapalı');
|
|
2208
|
+
});
|
|
2209
|
+
$('#ttsTest').addEventListener('click', async () => {
|
|
2210
|
+
/* motor + IPC + çalma zincirini uçtan uca dener — sorun neredeyse görünür */
|
|
2211
|
+
toast('TTS test ediliyor…');
|
|
2212
|
+
const r = await beast.ttsSpeak('Merhaba kanka, Edge TTS testi. Ben Beast.').catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
2213
|
+
if (!(r && r.ok)) { toast('TTS test HATA: ' + ((r && r.error) || '?')); return; }
|
|
2214
|
+
try {
|
|
2215
|
+
await playTtsB64(r);
|
|
2216
|
+
toast('TTS test OK — ses geliyor');
|
|
2217
|
+
} catch (e) {
|
|
2218
|
+
toast('TTS test çalma hatası: ' + String((e && e.message) || e));
|
|
2219
|
+
}
|
|
1926
2220
|
});
|
|
1927
2221
|
}
|
|
1928
2222
|
|
|
@@ -4420,10 +4714,17 @@ function onEvent(ev) {
|
|
|
4420
4714
|
if (shown && ev.width) document.body.style.setProperty('--bw', ev.width + 'px');
|
|
4421
4715
|
document.body.classList.toggle('phone-mode', !!ev.phone);
|
|
4422
4716
|
if (els.bbPhone) els.bbPhone.classList.toggle('on', !!ev.phone);
|
|
4717
|
+
/* MOBİL ÖNİZLEME: telefon silueti çerçevesi + düğme durumu + cihaz seçimi */
|
|
4718
|
+
document.body.classList.toggle('mobile-preview', !!ev.mobile);
|
|
4719
|
+
if (els.bbMobile) els.bbMobile.classList.toggle('on', !!ev.mobile);
|
|
4720
|
+
if (ev.device) {
|
|
4721
|
+
document.body.dataset.pfDevice = ev.device;
|
|
4722
|
+
if (els.bbDevice) els.bbDevice.value = ev.device;
|
|
4723
|
+
}
|
|
4724
|
+
renderPhoneFrame(ev.phoneRect || null);
|
|
4423
4725
|
els.browserBar.hidden = !shown;
|
|
4424
4726
|
els.bbResize.hidden = !shown;
|
|
4425
|
-
/* terminal
|
|
4426
|
-
if (shown && termOpen) termSetOpen(false);
|
|
4727
|
+
/* terminal artık ALT dock — tarayıcıyla birlikte yaşar, kapatılmaz */
|
|
4427
4728
|
/* #19 tarayıcı açılınca paralel ajan konsolu (sağ panel) yerini bırakır;
|
|
4428
4729
|
kapanınca önceki durumuna döner — istenirse railBtn ile elle açılır.
|
|
4429
4730
|
GİZLİ ajan gezinmeleri (shown=false, wasShown=false) rail'e DOKUNMAZ —
|
|
@@ -4485,10 +4786,20 @@ function onEvent(ev) {
|
|
|
4485
4786
|
break;
|
|
4486
4787
|
case 'done':
|
|
4487
4788
|
closeChatToolGroup();
|
|
4488
|
-
/* iptal
|
|
4489
|
-
|
|
4789
|
+
/* iptal sebebini SOHBETE yaz — ANCAK sesle kesilen turlarda not DÜŞÜLMEZ
|
|
4790
|
+
("■ durdurma" hayaleti olmasın; handsfree'in kendi iş akışı) */
|
|
4791
|
+
if (ev.aborted) {
|
|
4792
|
+
if (!/eller serbest/i.test(String(ev.reason || ''))) addStopNote(ev.reason);
|
|
4793
|
+
ttsQueueReset();
|
|
4794
|
+
}
|
|
4490
4795
|
setBusy(false);
|
|
4491
|
-
setStatus('');
|
|
4796
|
+
setStatus('');
|
|
4797
|
+
/* TTS (eski hal): cevap bitince TAMAMINI seslendir */
|
|
4798
|
+
if (!ev.aborted && (!ev.sessionId || String(ev.sessionId) === String(activeId))) {
|
|
4799
|
+
const mdEl = [...els.msgs.querySelectorAll('.msg-assistant .md')].pop();
|
|
4800
|
+
const replyText = mdEl ? mdEl.innerText.trim() : '';
|
|
4801
|
+
if (replyText) void speakText(replyText);
|
|
4802
|
+
}
|
|
4492
4803
|
/* cevabın SONU görünsün: markdown son render sonrası iki kez en alta kilitle */
|
|
4493
4804
|
scrollDown(true);
|
|
4494
4805
|
setTimeout(() => scrollDown(true), 80);
|
|
@@ -4498,6 +4809,7 @@ function onEvent(ev) {
|
|
|
4498
4809
|
break;
|
|
4499
4810
|
case 'error':
|
|
4500
4811
|
closeChatToolGroup();
|
|
4812
|
+
ttsQueueReset(); // hata — konuşma kuyruğunu da temizle
|
|
4501
4813
|
setBusy(false);
|
|
4502
4814
|
setStatus('');
|
|
4503
4815
|
addErrorBubble(ev.error);
|
|
@@ -4559,17 +4871,28 @@ function termSetShell(s) {
|
|
|
4559
4871
|
if (changed && termOpen && termBannerDone) termLine('t-sys', 'Kabuk: ' + TERM_SHELLS[s].label);
|
|
4560
4872
|
}
|
|
4561
4873
|
|
|
4562
|
-
function
|
|
4563
|
-
|
|
4564
|
-
|
|
4874
|
+
function termSetHeight(h) {
|
|
4875
|
+
const v = Math.max(120, Math.min(Math.round(h), Math.round(window.innerHeight * 0.7)));
|
|
4876
|
+
document.body.style.setProperty('--th', v + 'px');
|
|
4877
|
+
try { localStorage.setItem('beast.termH', String(v)); } catch {}
|
|
4878
|
+
/* native tarayıcı view'ına alt payı bildir — view yüksekliğini kısar */
|
|
4879
|
+
beast.browserSetBottomInset(termOpen ? v : 0).catch(() => {});
|
|
4565
4880
|
}
|
|
4566
4881
|
|
|
4567
4882
|
function termSetOpen(v) {
|
|
4883
|
+
/* BEAST CODE modunda terminal HEP AÇIK: kapatma denemeleri IDE modunda yok sayılır */
|
|
4884
|
+
if (!v && ideModeOn()) {
|
|
4885
|
+
toast('Beast Code modunda terminal açık kalır');
|
|
4886
|
+
return;
|
|
4887
|
+
}
|
|
4568
4888
|
termOpen = !!v;
|
|
4569
4889
|
els.termPanel.hidden = !v;
|
|
4570
4890
|
els.termResize.hidden = !v;
|
|
4571
4891
|
document.body.classList.toggle('term-open', v);
|
|
4572
4892
|
termSetShell(termShell);
|
|
4893
|
+
/* alt dock: tarayıcı view'ı terminal payını boşaltır */
|
|
4894
|
+
const th = parseInt(getComputedStyle(document.body).getPropertyValue('--th')) || 180;
|
|
4895
|
+
beast.browserSetBottomInset(v ? th : 0).catch(() => {});
|
|
4573
4896
|
if (v) {
|
|
4574
4897
|
els.termInput.focus();
|
|
4575
4898
|
termScroll(true);
|
|
@@ -4611,6 +4934,56 @@ function termShortTool(name, args) {
|
|
|
4611
4934
|
} catch { return ''; }
|
|
4612
4935
|
}
|
|
4613
4936
|
|
|
4937
|
+
/* ---------------- MOBİL ÖNİZLEME: telefon silueti ----------------
|
|
4938
|
+
main, WebContentsView'ı cihaz EKRANI boyutuna küçültür (phoneRect olayıyla
|
|
4939
|
+
gelir); çerçeve TEK YUVARLAK HALKA (border 12px, radius 42 — üst/alt köşeler
|
|
4940
|
+
birebir aynı) DOM'da çizilir; home çubuğu alt kenar içindedir. */
|
|
4941
|
+
const PF_BEZEL = 12;
|
|
4942
|
+
|
|
4943
|
+
function renderPhoneFrame(rect) {
|
|
4944
|
+
const pk = document.getElementById('bbDevice');
|
|
4945
|
+
let f = document.getElementById('phoneFrame');
|
|
4946
|
+
if (!rect) {
|
|
4947
|
+
if (f) f.style.display = 'none';
|
|
4948
|
+
if (pk) pk.style.display = 'none';
|
|
4949
|
+
return;
|
|
4950
|
+
}
|
|
4951
|
+
if (!f) {
|
|
4952
|
+
f = document.createElement('div');
|
|
4953
|
+
f.id = 'phoneFrame';
|
|
4954
|
+
f.innerHTML = '<div class="pf-home"></div>';
|
|
4955
|
+
document.body.appendChild(f);
|
|
4956
|
+
}
|
|
4957
|
+
f.style.display = 'block';
|
|
4958
|
+
f.style.left = (rect.x - PF_BEZEL) + 'px';
|
|
4959
|
+
f.style.top = (rect.y - PF_BEZEL) + 'px';
|
|
4960
|
+
f.style.width = (rect.width + PF_BEZEL * 2) + 'px';
|
|
4961
|
+
f.style.height = (rect.height + PF_BEZEL * 2) + 'px';
|
|
4962
|
+
/* cihaz seçici: çerçevenin altında ortalanmış */
|
|
4963
|
+
if (pk) {
|
|
4964
|
+
const frameBottom = rect.y + rect.height + PF_BEZEL;
|
|
4965
|
+
pk.hidden = false;
|
|
4966
|
+
pk.style.display = 'block';
|
|
4967
|
+
pk.style.top = (frameBottom + 12) + 'px';
|
|
4968
|
+
pk.style.left = Math.max(8, rect.x + rect.width / 2 - pk.offsetWidth / 2) + 'px';
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
|
|
4972
|
+
/* ajan dev sunucu başlattı mı? (expo/metro/vite/next) — mobil önizleme
|
|
4973
|
+
açıksa oraya otomatik gidilir, kapatılsa da son URL hatırlanır */
|
|
4974
|
+
let devSeenUrl = '';
|
|
4975
|
+
const DEV_URL_RE_R = /https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|(?:192\.168|10\.\d+|172\.(?:1[6-9]|2\d|3[01]))\.\d+\.\d+):(\d{2,5})/;
|
|
4976
|
+
function devServerSeen(text) {
|
|
4977
|
+
const m = DEV_URL_RE_R.exec(String(text || ''));
|
|
4978
|
+
if (!m) return;
|
|
4979
|
+
const port = Number(m[1]);
|
|
4980
|
+
if (port === 19000 || port === 19001 || port === 19002) return; // expo native/log portları
|
|
4981
|
+
const url = m[0].replace(/\/+$/, '') + '/';
|
|
4982
|
+
if (devSeenUrl === url) return;
|
|
4983
|
+
devSeenUrl = url;
|
|
4984
|
+
if (document.body.classList.contains('mobile-preview')) beast.browserNavigate(url).catch(() => {});
|
|
4985
|
+
}
|
|
4986
|
+
|
|
4614
4987
|
/* ajan araç etkinliğini terminale akıt (tüm oturumlar: ana sohbet + paralel ajanlar + cron) */
|
|
4615
4988
|
function termAgentEvent(ev) {
|
|
4616
4989
|
if (ev.type === 'tool-start') {
|
|
@@ -4618,6 +4991,8 @@ function termAgentEvent(ev) {
|
|
|
4618
4991
|
const args = termShortTool(ev.name, ev.args);
|
|
4619
4992
|
termLine('t-agent', sid + '▸ ' + ev.name + (args ? ': ' + args : ''));
|
|
4620
4993
|
} else if (ev.type === 'tool-end') {
|
|
4994
|
+
/* mobil önizleme: dev sunucu çıktısı yakala ("Local: http://localhost:8081" vb.) */
|
|
4995
|
+
devServerSeen(ev.result);
|
|
4621
4996
|
const shellish = /bash|shell|powershell|cmd|command|script|terminal/i.test(String(ev.name || ''));
|
|
4622
4997
|
const out = String(ev.result || '').replace(/\s+$/, '');
|
|
4623
4998
|
const cap = shellish ? 1600 : 240;
|
|
@@ -4929,6 +5304,7 @@ async function saveVisible(v) {
|
|
|
4929
5304
|
async function sendCurrent() {
|
|
4930
5305
|
const text = els.input.value.trim();
|
|
4931
5306
|
if ((!text && !pending.length) || !activeId) return;
|
|
5307
|
+
ttsQueueReset(); // yeni tur — eski seslendirme kuyruğu temizlenir
|
|
4932
5308
|
/* #25 /clear artık main'e gider: oturum kayıtları GERÇEKTEN silinir
|
|
4933
5309
|
(engine.clearMessages) ve 'clear' olayıyla ekran da temizlenir */
|
|
4934
5310
|
if (text === '/screenshot') {
|
|
@@ -5170,7 +5546,10 @@ async function init() {
|
|
|
5170
5546
|
refreshAgentsPane();
|
|
5171
5547
|
|
|
5172
5548
|
els.sendBtn.addEventListener('click', sendCurrent);
|
|
5173
|
-
els.stopBtn.addEventListener('click', () =>
|
|
5549
|
+
els.stopBtn.addEventListener('click', () => {
|
|
5550
|
+
ttsQueueReset(); // ■ = konuşan ajan da susturulur
|
|
5551
|
+
if (activeId) beast.interrupt(activeId);
|
|
5552
|
+
});
|
|
5174
5553
|
|
|
5175
5554
|
els.input.addEventListener('keydown', (e) => {
|
|
5176
5555
|
if (!els.slashMenu.hidden) {
|
|
@@ -5282,68 +5661,88 @@ async function init() {
|
|
|
5282
5661
|
els.fileInput.value = '';
|
|
5283
5662
|
});
|
|
5284
5663
|
|
|
5285
|
-
/* ----
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
new
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
fr.readAsDataURL(b);
|
|
5297
|
-
});
|
|
5298
|
-
btn.addEventListener('click', async () => {
|
|
5299
|
-
if (micRec && micRec.state === 'recording') {
|
|
5300
|
-
micRec.stop();
|
|
5301
|
-
return;
|
|
5302
|
-
}
|
|
5303
|
-
try {
|
|
5304
|
-
micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
5305
|
-
} catch {
|
|
5306
|
-
toast(_t('mic_denied'));
|
|
5307
|
-
return;
|
|
5308
|
-
}
|
|
5309
|
-
micChunks = [];
|
|
5310
|
-
micRec = new MediaRecorder(micStream);
|
|
5311
|
-
micRec.ondataavailable = (e) => { if (e.data && e.data.size) micChunks.push(e.data); };
|
|
5312
|
-
micRec.onstop = async () => {
|
|
5313
|
-
try { micStream.getTracks().forEach((t) => t.stop()); } catch {}
|
|
5314
|
-
btn.classList.remove('rec');
|
|
5315
|
-
const blob = new Blob(micChunks, { type: micRec.mimeType || 'audio/webm' });
|
|
5316
|
-
if (!blob.size) return;
|
|
5317
|
-
toast(_t('mic_transcribing'));
|
|
5318
|
-
const b64 = await blobToDataUrl(blob);
|
|
5319
|
-
/* arayüz dili = Whisper dili: TR ise Türkçe, EN ise İngilizce algılar */
|
|
5320
|
-
const uiLang = (window.I18N && window.I18N.lang) || 'tr';
|
|
5321
|
-
const r = await beast.sttTranscribe(b64, uiLang).catch(() => ({ ok: false, error: 'ipc' }));
|
|
5322
|
-
if (r && r.ok && r.text) apply(r.text);
|
|
5323
|
-
else toast(_t('mic_fail'));
|
|
5324
|
-
};
|
|
5325
|
-
micRec.start();
|
|
5326
|
-
btn.classList.add('rec');
|
|
5327
|
-
toast(_t('mic_listening'));
|
|
5664
|
+
/* ---- ELLER SERBEST KONUŞMA (Hermes portu — handsfree.js) ----
|
|
5665
|
+
Tek tık: sürekli dinleme döngüsü. Konuş, susunca cümle kendiliğinden
|
|
5666
|
+
kesilir → yazıya çevrilir → OTOMATİK GÖNDERİLİR → cevap bitince mikrofon
|
|
5667
|
+
yeniden açılır. "dur/yeter/goodbye" döngüyü bitirir. Tekrar tık (dinlerken)
|
|
5668
|
+
= şimdi gönder; düşünürken tık = modu kapat. Üç panelde aynı düzenek:
|
|
5669
|
+
chat, Beast Code, Beast Studio. */
|
|
5670
|
+
const hfBlobToDataUrl = (b) =>
|
|
5671
|
+
new Promise((res) => {
|
|
5672
|
+
const fr = new FileReader();
|
|
5673
|
+
fr.onload = () => res(fr.result);
|
|
5674
|
+
fr.readAsDataURL(b);
|
|
5328
5675
|
});
|
|
5676
|
+
/* STT çağrıları main'deki öncelikli sıraya gider: final (priority 1)
|
|
5677
|
+
önizlemelerin (0) önüne geçer — whisper CPU'da yavaş olsa bile
|
|
5678
|
+
konuşanın cevabı önce transkribe edilir */
|
|
5679
|
+
let hfSttChain = Promise.resolve();
|
|
5680
|
+
const hfTranscribe = (blob, isStale, priority) => {
|
|
5681
|
+
const run = async () => {
|
|
5682
|
+
if (isStale && isStale()) throw new Error('stale');
|
|
5683
|
+
const b64 = await hfBlobToDataUrl(blob);
|
|
5684
|
+
/* arayüz dili = Whisper dili: TR ise Türkçe, EN ise İngilizce algılar */
|
|
5685
|
+
const uiLang = (window.I18N && window.I18N.lang) || 'tr';
|
|
5686
|
+
const r = await beast.sttTranscribe(b64, uiLang, priority).catch(() => ({ ok: false, error: 'ipc' }));
|
|
5687
|
+
if (r && r.ok && r.text) return String(r.text);
|
|
5688
|
+
throw new Error((r && r.error) || 'stt-fail');
|
|
5689
|
+
};
|
|
5690
|
+
const p = hfSttChain.then(run, run);
|
|
5691
|
+
hfSttChain = p.then(() => {}, () => {});
|
|
5692
|
+
return p;
|
|
5329
5693
|
};
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5694
|
+
if (window.BeastHandsFree) {
|
|
5695
|
+
const mkHF = (cfg) => { if (cfg.btn) window.BeastHandsFree.createPanel(cfg); };
|
|
5696
|
+
/* DOĞAL KONUŞMA: barge tetiklenince TTS anında kesilir → mikrofon temizlenir */
|
|
5697
|
+
const cutTts = () => {
|
|
5698
|
+
try { if (ttsAudioEl) ttsAudioEl.pause(); } catch {}
|
|
5699
|
+
const st = window.BeastHandsFree && window.BeastHandsFree.ttsState;
|
|
5700
|
+
if (st) st.playing = false;
|
|
5701
|
+
};
|
|
5702
|
+
mkHF({
|
|
5703
|
+
btn: els.micBtn,
|
|
5704
|
+
isBusy: () => busy,
|
|
5705
|
+
submit: async (text) => { els.input.value = text; await sendCurrent(); },
|
|
5706
|
+
transcribe: hfTranscribe,
|
|
5707
|
+
draft: (text) => {
|
|
5708
|
+
els.input.value = text;
|
|
5709
|
+
els.input.dispatchEvent(new Event('input'));
|
|
5710
|
+
autosize();
|
|
5711
|
+
},
|
|
5712
|
+
interrupt: () => { if (activeId) beast.interrupt(activeId, 'eller serbest: konuşunca tur kesildi').catch(() => {}); },
|
|
5713
|
+
cutTts,
|
|
5714
|
+
status: (m) => setStatus(m),
|
|
5715
|
+
statusHide: () => setStatus(''),
|
|
5716
|
+
toast,
|
|
5717
|
+
t: _t,
|
|
5718
|
+
});
|
|
5719
|
+
mkHF({
|
|
5720
|
+
btn: els.bcMic,
|
|
5721
|
+
isBusy: () => bcRunning,
|
|
5722
|
+
submit: async (text) => { els.bcInput.value = text; bcRunCurrent(); },
|
|
5723
|
+
transcribe: hfTranscribe,
|
|
5724
|
+
draft: (text) => { els.bcInput.value = text; bcInputResize(); },
|
|
5725
|
+
interrupt: () => { beast.beastcodeStop().catch(() => {}); },
|
|
5726
|
+
cutTts,
|
|
5727
|
+
status: (m) => bcStatusShow(m),
|
|
5728
|
+
statusHide: () => bcStatusHide(),
|
|
5729
|
+
toast,
|
|
5730
|
+
t: _t,
|
|
5731
|
+
});
|
|
5732
|
+
mkHF({
|
|
5733
|
+
btn: els.stMic,
|
|
5734
|
+
isBusy: () => stRunning,
|
|
5735
|
+
submit: async (text) => { els.stInput.value = text; stRunCurrent(); },
|
|
5736
|
+
transcribe: hfTranscribe,
|
|
5737
|
+
draft: (text) => { els.stInput.value = text; stInputResize(); },
|
|
5738
|
+
interrupt: () => { beast.studioStop().catch(() => {}); },
|
|
5739
|
+
cutTts,
|
|
5740
|
+
status: (m) => stStatusShow(m),
|
|
5741
|
+
statusHide: () => stStatusHide(),
|
|
5742
|
+
toast,
|
|
5743
|
+
t: _t,
|
|
5744
|
+
});
|
|
5745
|
+
}
|
|
5347
5746
|
|
|
5348
5747
|
beast.onWaEvent(onWaEvent);
|
|
5349
5748
|
beast.onTgEvent(onTgEvent);
|
|
@@ -5400,8 +5799,25 @@ async function init() {
|
|
|
5400
5799
|
beast.browserPhone(on).catch(() => {});
|
|
5401
5800
|
});
|
|
5402
5801
|
}
|
|
5802
|
+
if (els.bbMobile) {
|
|
5803
|
+
els.bbMobile.addEventListener('click', () => {
|
|
5804
|
+
/* MOBİL ÖNİZLEME: telefon silueti içinde canlı dev-server önizlemesi */
|
|
5805
|
+
const on = !document.body.classList.contains('mobile-preview');
|
|
5806
|
+
beast.browserMobileSet(on).catch(() => {});
|
|
5807
|
+
});
|
|
5808
|
+
}
|
|
5809
|
+
if (els.bbDevice) {
|
|
5810
|
+
els.bbDevice.addEventListener('change', () => {
|
|
5811
|
+
beast.browserDeviceSet(els.bbDevice.value).catch(() => {});
|
|
5812
|
+
});
|
|
5813
|
+
}
|
|
5403
5814
|
els.bbClose.addEventListener('click', () => {
|
|
5404
5815
|
document.body.classList.remove('browser-open');
|
|
5816
|
+
/* mobil önizleme KESİN kapansın: main tarafı zaten kapatıyor, DOM tarafı da
|
|
5817
|
+
beklemeden temizlenir (siluet + cihaz seçici + mod düğmesi) */
|
|
5818
|
+
document.body.classList.remove('mobile-preview');
|
|
5819
|
+
if (els.bbMobile) els.bbMobile.classList.remove('on');
|
|
5820
|
+
renderPhoneFrame(null);
|
|
5405
5821
|
els.browserBar.hidden = true;
|
|
5406
5822
|
els.bbResize.hidden = true;
|
|
5407
5823
|
els.browserBtn.classList.remove('on');
|
|
@@ -5472,7 +5888,7 @@ async function init() {
|
|
|
5472
5888
|
});
|
|
5473
5889
|
|
|
5474
5890
|
/* terminal paneli — olay bağlama */
|
|
5475
|
-
try {
|
|
5891
|
+
try { termSetHeight(parseInt(localStorage.getItem('beast.termH')) || 180); } catch {}
|
|
5476
5892
|
if (els.termCBtn) els.termCBtn.addEventListener('click', () => termToggle('cmd'));
|
|
5477
5893
|
if (els.termClose) els.termClose.addEventListener('click', () => termSetOpen(false));
|
|
5478
5894
|
if (els.termClear) els.termClear.addEventListener('click', () => { els.termOut.innerHTML = ''; });
|
|
@@ -5495,18 +5911,19 @@ async function init() {
|
|
|
5495
5911
|
}
|
|
5496
5912
|
}
|
|
5497
5913
|
});
|
|
5498
|
-
/* terminal sürükle-boyutlandır */
|
|
5914
|
+
/* terminal sürükle-boyutlandır (alt dock: üst kenardan dikey sürükle) */
|
|
5499
5915
|
let trz = null;
|
|
5500
|
-
const
|
|
5916
|
+
const thNow = () => parseInt(getComputedStyle(document.body).getPropertyValue('--th')) || 180;
|
|
5501
5917
|
if (els.termResize) els.termResize.addEventListener('mousedown', (e) => {
|
|
5502
5918
|
e.preventDefault();
|
|
5503
|
-
trz = {
|
|
5919
|
+
trz = { sy: e.clientY, sh: thNow() };
|
|
5504
5920
|
document.body.classList.add('term-dragging');
|
|
5505
5921
|
});
|
|
5506
5922
|
document.addEventListener('mousemove', (e) => {
|
|
5507
5923
|
if (!trz) return;
|
|
5508
|
-
|
|
5509
|
-
|
|
5924
|
+
/* yukarı çek = yükseklik artar; aşağı çek = azalır */
|
|
5925
|
+
const h = Math.max(120, Math.min(trz.sh + (trz.sy - e.clientY), Math.round(window.innerHeight * 0.7)));
|
|
5926
|
+
termSetHeight(h);
|
|
5510
5927
|
});
|
|
5511
5928
|
document.addEventListener('mouseup', () => {
|
|
5512
5929
|
if (!trz) return;
|
|
@@ -6258,9 +6675,13 @@ async function setIdeMode(on) {
|
|
|
6258
6675
|
if (!on && document.body.classList.contains('browser-open')) {
|
|
6259
6676
|
try { beast.toggleBrowser(); } catch {}
|
|
6260
6677
|
}
|
|
6678
|
+
/* ALT TERMINAL: yalnız Beast Code'a özgü — Agent/Studio moduna dönüşte kapanır */
|
|
6679
|
+
if (!on && termOpen) termSetOpen(false);
|
|
6261
6680
|
if (on) {
|
|
6262
6681
|
ideSplitRestore();
|
|
6263
6682
|
setEditorHidden(localStorage.getItem('beast.editorHidden') === '1');
|
|
6683
|
+
/* BEAST CODE modunda terminal HEP AÇIK — mod girilirken otomatik açılır */
|
|
6684
|
+
if (!termOpen) termSetOpen(true);
|
|
6264
6685
|
await loadIdeTree();
|
|
6265
6686
|
bcBanner();
|
|
6266
6687
|
codeGutterRender(); /* dosya açık olmasa bile rakamlar görünür */
|
|
@@ -6291,6 +6712,8 @@ async function setStudioMode(on) {
|
|
|
6291
6712
|
if (on && ideModeOn()) await setIdeMode(false); /* IDE açıkken Studio'ya geçiş — IDE kapanır */
|
|
6292
6713
|
document.body.classList.toggle('studio-mode', !!on);
|
|
6293
6714
|
if (els.studioBtn) els.studioBtn.classList.toggle('on', !!on);
|
|
6715
|
+
/* ALT TERMINAL: yalnız Beast Code'a özgü — Studio'ya geçişte kapanır */
|
|
6716
|
+
if (on && termOpen) termSetOpen(false);
|
|
6294
6717
|
const brandSub = document.querySelector('#brand .brand-sub');
|
|
6295
6718
|
if (brandSub) brandSub.textContent = on ? 'Studio' : 'Agent';
|
|
6296
6719
|
/* klasör konsolunun en üstü: Studio modunda "BEAST STUDIO" yazar */
|
|
@@ -7091,6 +7514,8 @@ $('#filePreview').addEventListener('click', async () => {
|
|
|
7091
7514
|
const r = await beast.idePreview().catch(() => null);
|
|
7092
7515
|
if (r && r.ok) {
|
|
7093
7516
|
if (ideModeOn() === false) setIdeMode(true);
|
|
7517
|
+
/* mobil proje: npm run web + telefon silueti — kullanıcıya net geri bildirim */
|
|
7518
|
+
if (r.mobile) toast('Mobil önizleme: dev sunucu başlatıldı — telefon siluetine bak');
|
|
7094
7519
|
} else {
|
|
7095
7520
|
toast((r && r.error) || 'preview açılamadı');
|
|
7096
7521
|
}
|