beast-agent 2.2.1 → 2.3.1
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 +890 -57
- package/src/preload.js +7 -2
- package/src/renderer/handsfree.js +633 -0
- package/src/renderer/i18n.js +30 -6
- package/src/renderer/index.html +21 -7
- package/src/renderer/renderer.js +472 -83
- 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,293 @@ async function renderSkillsPane() {
|
|
|
1891
1894
|
|
|
1892
1895
|
/* ---------------- sesli yanıt (TTS) ---------------- */
|
|
1893
1896
|
|
|
1894
|
-
|
|
1895
|
-
|
|
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
|
+
}
|
|
2100
|
+
|
|
2101
|
+
async function renderTtsPane() { const pane = $('#tab-tts');
|
|
1896
2102
|
if (!pane) return;
|
|
2103
|
+
const edgeOpts = [
|
|
2104
|
+
['tr-TR-AhmetNeural', 'Ahmet — Türkçe (erkek)'],
|
|
2105
|
+
['tr-TR-EmelNeural', 'Emel — Türkçe (kadın)'],
|
|
2106
|
+
['en-US-GuyNeural', 'Guy — English (male)'],
|
|
2107
|
+
['en-US-AriaNeural', 'Aria — English (female)'],
|
|
2108
|
+
['de-DE-KatjaNeural', 'Katja — Deutsch'],
|
|
2109
|
+
['ar-SA-HamedNeural', 'Hamed — العربية'],
|
|
2110
|
+
]
|
|
2111
|
+
.map(([v, n]) => `<option value="${v}">${n}</option>`)
|
|
2112
|
+
.join('');
|
|
1897
2113
|
pane.innerHTML =
|
|
1898
2114
|
'<h2>' + _t('tts_h2') + '</h2><div class="sub">' + _t('tts_sub') + '</div>' +
|
|
1899
2115
|
`<div class="form-grid" style="grid-template-columns:auto 1fr 1fr;align-items:center;margin-top:10px">
|
|
1900
2116
|
<label class="lock-row"><input type="checkbox" id="ttsOn" /><span>${_t('tts_active')}</span></label>
|
|
1901
|
-
<
|
|
1902
|
-
<
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2117
|
+
<label style="grid-column:1">${_t('tts_engine')}</label>
|
|
2118
|
+
<select id="ttsEngine" class="inp" style="grid-column:2/4">
|
|
2119
|
+
<option value="edge">Edge TTS — yerel & ücretsiz (Ahmet/Emel…)</option>
|
|
2120
|
+
<option value="openai">OpenAI-uyumlu API (tts-1, ses: alloy…)</option>
|
|
2121
|
+
</select>
|
|
2122
|
+
<label style="grid-column:1">${_t('tts_edge_voice')}</label>
|
|
2123
|
+
<select id="ttsEdgeVoice" class="inp" style="grid-column:2/4">${edgeOpts}</select>
|
|
2124
|
+
<label class="lock-row" style="grid-column:1/4"><input type="checkbox" id="ttsChatAuto" /><span>${_t('tts_chat_auto')}</span></label>
|
|
2125
|
+
<span id="ttsOpenaiWrap" style="display:contents">
|
|
2126
|
+
<input id="ttsUrl" class="inp" placeholder="https://api.openai.com/v1" autocomplete="off" />
|
|
2127
|
+
<input id="ttsKey" class="inp" type="password" placeholder="API Key" autocomplete="off" />
|
|
2128
|
+
<input id="ttsModel" class="inp" placeholder="tts-1" autocomplete="off" />
|
|
2129
|
+
<input id="ttsVoice" class="inp" placeholder="ses: alloy" autocomplete="off" />
|
|
2130
|
+
</span>
|
|
2131
|
+
<button id="ttsSave" class="btn ghost" style="grid-column:1">${_t('tts_save')}</button>
|
|
2132
|
+
<button id="ttsTest" class="btn ghost" style="grid-column:2;justify-self:start">${_t('tts_test')}</button>
|
|
1906
2133
|
</div>` +
|
|
1907
2134
|
'<div class="sub" style="margin-top:8px">' + _t('tts_note') + '</div>';
|
|
1908
2135
|
|
|
2136
|
+
const syncEngineUi = () => {
|
|
2137
|
+
const isEdge = $('#ttsEngine').value === 'edge';
|
|
2138
|
+
$('#ttsEdgeVoice').disabled = !isEdge;
|
|
2139
|
+
$('#ttsOpenaiWrap').style.opacity = isEdge ? '0.35' : '1';
|
|
2140
|
+
$('#ttsOpenaiWrap').querySelectorAll('input').forEach((i) => (i.disabled = isEdge));
|
|
2141
|
+
};
|
|
2142
|
+
|
|
1909
2143
|
try {
|
|
1910
2144
|
const tts = await beast.waGetTts();
|
|
1911
2145
|
$('#ttsOn').checked = !!tts.enabled;
|
|
2146
|
+
$('#ttsEngine').value = tts.engine === 'openai' ? 'openai' : 'edge';
|
|
2147
|
+
$('#ttsEdgeVoice').value = tts.edgeVoice || 'tr-TR-AhmetNeural';
|
|
2148
|
+
$('#ttsChatAuto').checked = !!tts.chatAutoSpeak;
|
|
1912
2149
|
$('#ttsUrl').value = tts.baseUrl || '';
|
|
1913
2150
|
$('#ttsKey').value = tts.key || '';
|
|
1914
2151
|
$('#ttsModel').value = tts.model || 'tts-1';
|
|
1915
2152
|
$('#ttsVoice').value = tts.voice || 'alloy';
|
|
1916
2153
|
} catch {}
|
|
2154
|
+
syncEngineUi();
|
|
2155
|
+
$('#ttsEngine').addEventListener('change', syncEngineUi);
|
|
1917
2156
|
$('#ttsSave').addEventListener('click', async () => {
|
|
1918
2157
|
await beast.waSetTts({
|
|
1919
2158
|
enabled: $('#ttsOn').checked,
|
|
2159
|
+
engine: $('#ttsEngine').value,
|
|
2160
|
+
edgeVoice: $('#ttsEdgeVoice').value,
|
|
2161
|
+
chatAutoSpeak: $('#ttsChatAuto').checked,
|
|
1920
2162
|
baseUrl: $('#ttsUrl').value.trim(),
|
|
1921
2163
|
key: $('#ttsKey').value.trim(),
|
|
1922
2164
|
model: $('#ttsModel').value.trim(),
|
|
1923
2165
|
voice: $('#ttsVoice').value.trim(),
|
|
1924
2166
|
});
|
|
1925
|
-
|
|
2167
|
+
ttsCfgCache = null; // ayar önbelleğini tazele
|
|
2168
|
+
/* hoparlör düğmesiyle TEK doğruluk kaynağı senkronu — asıl seslendirmeme bug'ı */
|
|
2169
|
+
chatTtsOn = $('#ttsChatAuto').checked;
|
|
2170
|
+
if (els.ttsBtn) els.ttsBtn.classList.toggle('on', chatTtsOn);
|
|
2171
|
+
toast($('#ttsOn').checked ? 'TTS açık — cevaplar sesli' : 'TTS kapalı');
|
|
2172
|
+
});
|
|
2173
|
+
$('#ttsTest').addEventListener('click', async () => {
|
|
2174
|
+
/* motor + IPC + çalma zincirini uçtan uca dener — sorun neredeyse görünür */
|
|
2175
|
+
toast('TTS test ediliyor…');
|
|
2176
|
+
const r = await beast.ttsSpeak('Merhaba kanka, Edge TTS testi. Ben Beast.').catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
|
|
2177
|
+
if (!(r && r.ok)) { toast('TTS test HATA: ' + ((r && r.error) || '?')); return; }
|
|
2178
|
+
try {
|
|
2179
|
+
await playTtsB64(r);
|
|
2180
|
+
toast('TTS test OK — ses geliyor');
|
|
2181
|
+
} catch (e) {
|
|
2182
|
+
toast('TTS test çalma hatası: ' + String((e && e.message) || e));
|
|
2183
|
+
}
|
|
1926
2184
|
});
|
|
1927
2185
|
}
|
|
1928
2186
|
|
|
@@ -4420,10 +4678,17 @@ function onEvent(ev) {
|
|
|
4420
4678
|
if (shown && ev.width) document.body.style.setProperty('--bw', ev.width + 'px');
|
|
4421
4679
|
document.body.classList.toggle('phone-mode', !!ev.phone);
|
|
4422
4680
|
if (els.bbPhone) els.bbPhone.classList.toggle('on', !!ev.phone);
|
|
4681
|
+
/* MOBİL ÖNİZLEME: telefon silueti çerçevesi + düğme durumu + cihaz seçimi */
|
|
4682
|
+
document.body.classList.toggle('mobile-preview', !!ev.mobile);
|
|
4683
|
+
if (els.bbMobile) els.bbMobile.classList.toggle('on', !!ev.mobile);
|
|
4684
|
+
if (ev.device) {
|
|
4685
|
+
document.body.dataset.pfDevice = ev.device;
|
|
4686
|
+
if (els.bbDevice) els.bbDevice.value = ev.device;
|
|
4687
|
+
}
|
|
4688
|
+
renderPhoneFrame(ev.phoneRect || null);
|
|
4423
4689
|
els.browserBar.hidden = !shown;
|
|
4424
4690
|
els.bbResize.hidden = !shown;
|
|
4425
|
-
/* terminal
|
|
4426
|
-
if (shown && termOpen) termSetOpen(false);
|
|
4691
|
+
/* terminal artık ALT dock — tarayıcıyla birlikte yaşar, kapatılmaz */
|
|
4427
4692
|
/* #19 tarayıcı açılınca paralel ajan konsolu (sağ panel) yerini bırakır;
|
|
4428
4693
|
kapanınca önceki durumuna döner — istenirse railBtn ile elle açılır.
|
|
4429
4694
|
GİZLİ ajan gezinmeleri (shown=false, wasShown=false) rail'e DOKUNMAZ —
|
|
@@ -4485,10 +4750,20 @@ function onEvent(ev) {
|
|
|
4485
4750
|
break;
|
|
4486
4751
|
case 'done':
|
|
4487
4752
|
closeChatToolGroup();
|
|
4488
|
-
/* iptal
|
|
4489
|
-
|
|
4753
|
+
/* iptal sebebini SOHBETE yaz — ANCAK sesle kesilen turlarda not DÜŞÜLMEZ
|
|
4754
|
+
("■ durdurma" hayaleti olmasın; handsfree'in kendi iş akışı) */
|
|
4755
|
+
if (ev.aborted) {
|
|
4756
|
+
if (!/eller serbest/i.test(String(ev.reason || ''))) addStopNote(ev.reason);
|
|
4757
|
+
ttsQueueReset();
|
|
4758
|
+
}
|
|
4490
4759
|
setBusy(false);
|
|
4491
|
-
setStatus('');
|
|
4760
|
+
setStatus('');
|
|
4761
|
+
/* TTS (eski hal): cevap bitince TAMAMINI seslendir */
|
|
4762
|
+
if (!ev.aborted && (!ev.sessionId || String(ev.sessionId) === String(activeId))) {
|
|
4763
|
+
const mdEl = [...els.msgs.querySelectorAll('.msg-assistant .md')].pop();
|
|
4764
|
+
const replyText = mdEl ? mdEl.innerText.trim() : '';
|
|
4765
|
+
if (replyText) void speakText(replyText);
|
|
4766
|
+
}
|
|
4492
4767
|
/* cevabın SONU görünsün: markdown son render sonrası iki kez en alta kilitle */
|
|
4493
4768
|
scrollDown(true);
|
|
4494
4769
|
setTimeout(() => scrollDown(true), 80);
|
|
@@ -4498,6 +4773,7 @@ function onEvent(ev) {
|
|
|
4498
4773
|
break;
|
|
4499
4774
|
case 'error':
|
|
4500
4775
|
closeChatToolGroup();
|
|
4776
|
+
ttsQueueReset(); // hata — konuşma kuyruğunu da temizle
|
|
4501
4777
|
setBusy(false);
|
|
4502
4778
|
setStatus('');
|
|
4503
4779
|
addErrorBubble(ev.error);
|
|
@@ -4559,17 +4835,28 @@ function termSetShell(s) {
|
|
|
4559
4835
|
if (changed && termOpen && termBannerDone) termLine('t-sys', 'Kabuk: ' + TERM_SHELLS[s].label);
|
|
4560
4836
|
}
|
|
4561
4837
|
|
|
4562
|
-
function
|
|
4563
|
-
|
|
4564
|
-
|
|
4838
|
+
function termSetHeight(h) {
|
|
4839
|
+
const v = Math.max(120, Math.min(Math.round(h), Math.round(window.innerHeight * 0.7)));
|
|
4840
|
+
document.body.style.setProperty('--th', v + 'px');
|
|
4841
|
+
try { localStorage.setItem('beast.termH', String(v)); } catch {}
|
|
4842
|
+
/* native tarayıcı view'ına alt payı bildir — view yüksekliğini kısar */
|
|
4843
|
+
beast.browserSetBottomInset(termOpen ? v : 0).catch(() => {});
|
|
4565
4844
|
}
|
|
4566
4845
|
|
|
4567
4846
|
function termSetOpen(v) {
|
|
4847
|
+
/* BEAST CODE modunda terminal HEP AÇIK: kapatma denemeleri IDE modunda yok sayılır */
|
|
4848
|
+
if (!v && ideModeOn()) {
|
|
4849
|
+
toast('Beast Code modunda terminal açık kalır');
|
|
4850
|
+
return;
|
|
4851
|
+
}
|
|
4568
4852
|
termOpen = !!v;
|
|
4569
4853
|
els.termPanel.hidden = !v;
|
|
4570
4854
|
els.termResize.hidden = !v;
|
|
4571
4855
|
document.body.classList.toggle('term-open', v);
|
|
4572
4856
|
termSetShell(termShell);
|
|
4857
|
+
/* alt dock: tarayıcı view'ı terminal payını boşaltır */
|
|
4858
|
+
const th = parseInt(getComputedStyle(document.body).getPropertyValue('--th')) || 180;
|
|
4859
|
+
beast.browserSetBottomInset(v ? th : 0).catch(() => {});
|
|
4573
4860
|
if (v) {
|
|
4574
4861
|
els.termInput.focus();
|
|
4575
4862
|
termScroll(true);
|
|
@@ -4611,6 +4898,56 @@ function termShortTool(name, args) {
|
|
|
4611
4898
|
} catch { return ''; }
|
|
4612
4899
|
}
|
|
4613
4900
|
|
|
4901
|
+
/* ---------------- MOBİL ÖNİZLEME: telefon silueti ----------------
|
|
4902
|
+
main, WebContentsView'ı cihaz EKRANI boyutuna küçültür (phoneRect olayıyla
|
|
4903
|
+
gelir); çerçeve TEK YUVARLAK HALKA (border 12px, radius 42 — üst/alt köşeler
|
|
4904
|
+
birebir aynı) DOM'da çizilir; home çubuğu alt kenar içindedir. */
|
|
4905
|
+
const PF_BEZEL = 12;
|
|
4906
|
+
|
|
4907
|
+
function renderPhoneFrame(rect) {
|
|
4908
|
+
const pk = document.getElementById('bbDevice');
|
|
4909
|
+
let f = document.getElementById('phoneFrame');
|
|
4910
|
+
if (!rect) {
|
|
4911
|
+
if (f) f.style.display = 'none';
|
|
4912
|
+
if (pk) pk.style.display = 'none';
|
|
4913
|
+
return;
|
|
4914
|
+
}
|
|
4915
|
+
if (!f) {
|
|
4916
|
+
f = document.createElement('div');
|
|
4917
|
+
f.id = 'phoneFrame';
|
|
4918
|
+
f.innerHTML = '<div class="pf-home"></div>';
|
|
4919
|
+
document.body.appendChild(f);
|
|
4920
|
+
}
|
|
4921
|
+
f.style.display = 'block';
|
|
4922
|
+
f.style.left = (rect.x - PF_BEZEL) + 'px';
|
|
4923
|
+
f.style.top = (rect.y - PF_BEZEL) + 'px';
|
|
4924
|
+
f.style.width = (rect.width + PF_BEZEL * 2) + 'px';
|
|
4925
|
+
f.style.height = (rect.height + PF_BEZEL * 2) + 'px';
|
|
4926
|
+
/* cihaz seçici: çerçevenin altında ortalanmış */
|
|
4927
|
+
if (pk) {
|
|
4928
|
+
const frameBottom = rect.y + rect.height + PF_BEZEL;
|
|
4929
|
+
pk.hidden = false;
|
|
4930
|
+
pk.style.display = 'block';
|
|
4931
|
+
pk.style.top = (frameBottom + 12) + 'px';
|
|
4932
|
+
pk.style.left = Math.max(8, rect.x + rect.width / 2 - pk.offsetWidth / 2) + 'px';
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
|
|
4936
|
+
/* ajan dev sunucu başlattı mı? (expo/metro/vite/next) — mobil önizleme
|
|
4937
|
+
açıksa oraya otomatik gidilir, kapatılsa da son URL hatırlanır */
|
|
4938
|
+
let devSeenUrl = '';
|
|
4939
|
+
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})/;
|
|
4940
|
+
function devServerSeen(text) {
|
|
4941
|
+
const m = DEV_URL_RE_R.exec(String(text || ''));
|
|
4942
|
+
if (!m) return;
|
|
4943
|
+
const port = Number(m[1]);
|
|
4944
|
+
if (port === 19000 || port === 19001 || port === 19002) return; // expo native/log portları
|
|
4945
|
+
const url = m[0].replace(/\/+$/, '') + '/';
|
|
4946
|
+
if (devSeenUrl === url) return;
|
|
4947
|
+
devSeenUrl = url;
|
|
4948
|
+
if (document.body.classList.contains('mobile-preview')) beast.browserNavigate(url).catch(() => {});
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4614
4951
|
/* ajan araç etkinliğini terminale akıt (tüm oturumlar: ana sohbet + paralel ajanlar + cron) */
|
|
4615
4952
|
function termAgentEvent(ev) {
|
|
4616
4953
|
if (ev.type === 'tool-start') {
|
|
@@ -4618,6 +4955,8 @@ function termAgentEvent(ev) {
|
|
|
4618
4955
|
const args = termShortTool(ev.name, ev.args);
|
|
4619
4956
|
termLine('t-agent', sid + '▸ ' + ev.name + (args ? ': ' + args : ''));
|
|
4620
4957
|
} else if (ev.type === 'tool-end') {
|
|
4958
|
+
/* mobil önizleme: dev sunucu çıktısı yakala ("Local: http://localhost:8081" vb.) */
|
|
4959
|
+
devServerSeen(ev.result);
|
|
4621
4960
|
const shellish = /bash|shell|powershell|cmd|command|script|terminal/i.test(String(ev.name || ''));
|
|
4622
4961
|
const out = String(ev.result || '').replace(/\s+$/, '');
|
|
4623
4962
|
const cap = shellish ? 1600 : 240;
|
|
@@ -4929,6 +5268,7 @@ async function saveVisible(v) {
|
|
|
4929
5268
|
async function sendCurrent() {
|
|
4930
5269
|
const text = els.input.value.trim();
|
|
4931
5270
|
if ((!text && !pending.length) || !activeId) return;
|
|
5271
|
+
ttsQueueReset(); // yeni tur — eski seslendirme kuyruğu temizlenir
|
|
4932
5272
|
/* #25 /clear artık main'e gider: oturum kayıtları GERÇEKTEN silinir
|
|
4933
5273
|
(engine.clearMessages) ve 'clear' olayıyla ekran da temizlenir */
|
|
4934
5274
|
if (text === '/screenshot') {
|
|
@@ -5170,7 +5510,10 @@ async function init() {
|
|
|
5170
5510
|
refreshAgentsPane();
|
|
5171
5511
|
|
|
5172
5512
|
els.sendBtn.addEventListener('click', sendCurrent);
|
|
5173
|
-
els.stopBtn.addEventListener('click', () =>
|
|
5513
|
+
els.stopBtn.addEventListener('click', () => {
|
|
5514
|
+
ttsQueueReset(); // ■ = konuşan ajan da susturulur
|
|
5515
|
+
if (activeId) beast.interrupt(activeId);
|
|
5516
|
+
});
|
|
5174
5517
|
|
|
5175
5518
|
els.input.addEventListener('keydown', (e) => {
|
|
5176
5519
|
if (!els.slashMenu.hidden) {
|
|
@@ -5282,68 +5625,88 @@ async function init() {
|
|
|
5282
5625
|
els.fileInput.value = '';
|
|
5283
5626
|
});
|
|
5284
5627
|
|
|
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'));
|
|
5628
|
+
/* ---- ELLER SERBEST KONUŞMA (Hermes portu — handsfree.js) ----
|
|
5629
|
+
Tek tık: sürekli dinleme döngüsü. Konuş, susunca cümle kendiliğinden
|
|
5630
|
+
kesilir → yazıya çevrilir → OTOMATİK GÖNDERİLİR → cevap bitince mikrofon
|
|
5631
|
+
yeniden açılır. "dur/yeter/goodbye" döngüyü bitirir. Tekrar tık (dinlerken)
|
|
5632
|
+
= şimdi gönder; düşünürken tık = modu kapat. Üç panelde aynı düzenek:
|
|
5633
|
+
chat, Beast Code, Beast Studio. */
|
|
5634
|
+
const hfBlobToDataUrl = (b) =>
|
|
5635
|
+
new Promise((res) => {
|
|
5636
|
+
const fr = new FileReader();
|
|
5637
|
+
fr.onload = () => res(fr.result);
|
|
5638
|
+
fr.readAsDataURL(b);
|
|
5328
5639
|
});
|
|
5640
|
+
/* STT çağrıları main'deki öncelikli sıraya gider: final (priority 1)
|
|
5641
|
+
önizlemelerin (0) önüne geçer — whisper CPU'da yavaş olsa bile
|
|
5642
|
+
konuşanın cevabı önce transkribe edilir */
|
|
5643
|
+
let hfSttChain = Promise.resolve();
|
|
5644
|
+
const hfTranscribe = (blob, isStale, priority) => {
|
|
5645
|
+
const run = async () => {
|
|
5646
|
+
if (isStale && isStale()) throw new Error('stale');
|
|
5647
|
+
const b64 = await hfBlobToDataUrl(blob);
|
|
5648
|
+
/* arayüz dili = Whisper dili: TR ise Türkçe, EN ise İngilizce algılar */
|
|
5649
|
+
const uiLang = (window.I18N && window.I18N.lang) || 'tr';
|
|
5650
|
+
const r = await beast.sttTranscribe(b64, uiLang, priority).catch(() => ({ ok: false, error: 'ipc' }));
|
|
5651
|
+
if (r && r.ok && r.text) return String(r.text);
|
|
5652
|
+
throw new Error((r && r.error) || 'stt-fail');
|
|
5653
|
+
};
|
|
5654
|
+
const p = hfSttChain.then(run, run);
|
|
5655
|
+
hfSttChain = p.then(() => {}, () => {});
|
|
5656
|
+
return p;
|
|
5329
5657
|
};
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5658
|
+
if (window.BeastHandsFree) {
|
|
5659
|
+
const mkHF = (cfg) => { if (cfg.btn) window.BeastHandsFree.createPanel(cfg); };
|
|
5660
|
+
/* DOĞAL KONUŞMA: barge tetiklenince TTS anında kesilir → mikrofon temizlenir */
|
|
5661
|
+
const cutTts = () => {
|
|
5662
|
+
try { if (ttsAudioEl) ttsAudioEl.pause(); } catch {}
|
|
5663
|
+
const st = window.BeastHandsFree && window.BeastHandsFree.ttsState;
|
|
5664
|
+
if (st) st.playing = false;
|
|
5665
|
+
};
|
|
5666
|
+
mkHF({
|
|
5667
|
+
btn: els.micBtn,
|
|
5668
|
+
isBusy: () => busy,
|
|
5669
|
+
submit: async (text) => { els.input.value = text; await sendCurrent(); },
|
|
5670
|
+
transcribe: hfTranscribe,
|
|
5671
|
+
draft: (text) => {
|
|
5672
|
+
els.input.value = text;
|
|
5673
|
+
els.input.dispatchEvent(new Event('input'));
|
|
5674
|
+
autosize();
|
|
5675
|
+
},
|
|
5676
|
+
interrupt: () => { if (activeId) beast.interrupt(activeId, 'eller serbest: konuşunca tur kesildi').catch(() => {}); },
|
|
5677
|
+
cutTts,
|
|
5678
|
+
status: (m) => setStatus(m),
|
|
5679
|
+
statusHide: () => setStatus(''),
|
|
5680
|
+
toast,
|
|
5681
|
+
t: _t,
|
|
5682
|
+
});
|
|
5683
|
+
mkHF({
|
|
5684
|
+
btn: els.bcMic,
|
|
5685
|
+
isBusy: () => bcRunning,
|
|
5686
|
+
submit: async (text) => { els.bcInput.value = text; bcRunCurrent(); },
|
|
5687
|
+
transcribe: hfTranscribe,
|
|
5688
|
+
draft: (text) => { els.bcInput.value = text; bcInputResize(); },
|
|
5689
|
+
interrupt: () => { beast.beastcodeStop().catch(() => {}); },
|
|
5690
|
+
cutTts,
|
|
5691
|
+
status: (m) => bcStatusShow(m),
|
|
5692
|
+
statusHide: () => bcStatusHide(),
|
|
5693
|
+
toast,
|
|
5694
|
+
t: _t,
|
|
5695
|
+
});
|
|
5696
|
+
mkHF({
|
|
5697
|
+
btn: els.stMic,
|
|
5698
|
+
isBusy: () => stRunning,
|
|
5699
|
+
submit: async (text) => { els.stInput.value = text; stRunCurrent(); },
|
|
5700
|
+
transcribe: hfTranscribe,
|
|
5701
|
+
draft: (text) => { els.stInput.value = text; stInputResize(); },
|
|
5702
|
+
interrupt: () => { beast.studioStop().catch(() => {}); },
|
|
5703
|
+
cutTts,
|
|
5704
|
+
status: (m) => stStatusShow(m),
|
|
5705
|
+
statusHide: () => stStatusHide(),
|
|
5706
|
+
toast,
|
|
5707
|
+
t: _t,
|
|
5708
|
+
});
|
|
5709
|
+
}
|
|
5347
5710
|
|
|
5348
5711
|
beast.onWaEvent(onWaEvent);
|
|
5349
5712
|
beast.onTgEvent(onTgEvent);
|
|
@@ -5400,8 +5763,25 @@ async function init() {
|
|
|
5400
5763
|
beast.browserPhone(on).catch(() => {});
|
|
5401
5764
|
});
|
|
5402
5765
|
}
|
|
5766
|
+
if (els.bbMobile) {
|
|
5767
|
+
els.bbMobile.addEventListener('click', () => {
|
|
5768
|
+
/* MOBİL ÖNİZLEME: telefon silueti içinde canlı dev-server önizlemesi */
|
|
5769
|
+
const on = !document.body.classList.contains('mobile-preview');
|
|
5770
|
+
beast.browserMobileSet(on).catch(() => {});
|
|
5771
|
+
});
|
|
5772
|
+
}
|
|
5773
|
+
if (els.bbDevice) {
|
|
5774
|
+
els.bbDevice.addEventListener('change', () => {
|
|
5775
|
+
beast.browserDeviceSet(els.bbDevice.value).catch(() => {});
|
|
5776
|
+
});
|
|
5777
|
+
}
|
|
5403
5778
|
els.bbClose.addEventListener('click', () => {
|
|
5404
5779
|
document.body.classList.remove('browser-open');
|
|
5780
|
+
/* mobil önizleme KESİN kapansın: main tarafı zaten kapatıyor, DOM tarafı da
|
|
5781
|
+
beklemeden temizlenir (siluet + cihaz seçici + mod düğmesi) */
|
|
5782
|
+
document.body.classList.remove('mobile-preview');
|
|
5783
|
+
if (els.bbMobile) els.bbMobile.classList.remove('on');
|
|
5784
|
+
renderPhoneFrame(null);
|
|
5405
5785
|
els.browserBar.hidden = true;
|
|
5406
5786
|
els.bbResize.hidden = true;
|
|
5407
5787
|
els.browserBtn.classList.remove('on');
|
|
@@ -5472,7 +5852,7 @@ async function init() {
|
|
|
5472
5852
|
});
|
|
5473
5853
|
|
|
5474
5854
|
/* terminal paneli — olay bağlama */
|
|
5475
|
-
try {
|
|
5855
|
+
try { termSetHeight(parseInt(localStorage.getItem('beast.termH')) || 180); } catch {}
|
|
5476
5856
|
if (els.termCBtn) els.termCBtn.addEventListener('click', () => termToggle('cmd'));
|
|
5477
5857
|
if (els.termClose) els.termClose.addEventListener('click', () => termSetOpen(false));
|
|
5478
5858
|
if (els.termClear) els.termClear.addEventListener('click', () => { els.termOut.innerHTML = ''; });
|
|
@@ -5495,18 +5875,19 @@ async function init() {
|
|
|
5495
5875
|
}
|
|
5496
5876
|
}
|
|
5497
5877
|
});
|
|
5498
|
-
/* terminal sürükle-boyutlandır */
|
|
5878
|
+
/* terminal sürükle-boyutlandır (alt dock: üst kenardan dikey sürükle) */
|
|
5499
5879
|
let trz = null;
|
|
5500
|
-
const
|
|
5880
|
+
const thNow = () => parseInt(getComputedStyle(document.body).getPropertyValue('--th')) || 180;
|
|
5501
5881
|
if (els.termResize) els.termResize.addEventListener('mousedown', (e) => {
|
|
5502
5882
|
e.preventDefault();
|
|
5503
|
-
trz = {
|
|
5883
|
+
trz = { sy: e.clientY, sh: thNow() };
|
|
5504
5884
|
document.body.classList.add('term-dragging');
|
|
5505
5885
|
});
|
|
5506
5886
|
document.addEventListener('mousemove', (e) => {
|
|
5507
5887
|
if (!trz) return;
|
|
5508
|
-
|
|
5509
|
-
|
|
5888
|
+
/* yukarı çek = yükseklik artar; aşağı çek = azalır */
|
|
5889
|
+
const h = Math.max(120, Math.min(trz.sh + (trz.sy - e.clientY), Math.round(window.innerHeight * 0.7)));
|
|
5890
|
+
termSetHeight(h);
|
|
5510
5891
|
});
|
|
5511
5892
|
document.addEventListener('mouseup', () => {
|
|
5512
5893
|
if (!trz) return;
|
|
@@ -6258,9 +6639,13 @@ async function setIdeMode(on) {
|
|
|
6258
6639
|
if (!on && document.body.classList.contains('browser-open')) {
|
|
6259
6640
|
try { beast.toggleBrowser(); } catch {}
|
|
6260
6641
|
}
|
|
6642
|
+
/* ALT TERMINAL: yalnız Beast Code'a özgü — Agent/Studio moduna dönüşte kapanır */
|
|
6643
|
+
if (!on && termOpen) termSetOpen(false);
|
|
6261
6644
|
if (on) {
|
|
6262
6645
|
ideSplitRestore();
|
|
6263
6646
|
setEditorHidden(localStorage.getItem('beast.editorHidden') === '1');
|
|
6647
|
+
/* BEAST CODE modunda terminal HEP AÇIK — mod girilirken otomatik açılır */
|
|
6648
|
+
if (!termOpen) termSetOpen(true);
|
|
6264
6649
|
await loadIdeTree();
|
|
6265
6650
|
bcBanner();
|
|
6266
6651
|
codeGutterRender(); /* dosya açık olmasa bile rakamlar görünür */
|
|
@@ -6291,6 +6676,8 @@ async function setStudioMode(on) {
|
|
|
6291
6676
|
if (on && ideModeOn()) await setIdeMode(false); /* IDE açıkken Studio'ya geçiş — IDE kapanır */
|
|
6292
6677
|
document.body.classList.toggle('studio-mode', !!on);
|
|
6293
6678
|
if (els.studioBtn) els.studioBtn.classList.toggle('on', !!on);
|
|
6679
|
+
/* ALT TERMINAL: yalnız Beast Code'a özgü — Studio'ya geçişte kapanır */
|
|
6680
|
+
if (on && termOpen) termSetOpen(false);
|
|
6294
6681
|
const brandSub = document.querySelector('#brand .brand-sub');
|
|
6295
6682
|
if (brandSub) brandSub.textContent = on ? 'Studio' : 'Agent';
|
|
6296
6683
|
/* klasör konsolunun en üstü: Studio modunda "BEAST STUDIO" yazar */
|
|
@@ -7091,6 +7478,8 @@ $('#filePreview').addEventListener('click', async () => {
|
|
|
7091
7478
|
const r = await beast.idePreview().catch(() => null);
|
|
7092
7479
|
if (r && r.ok) {
|
|
7093
7480
|
if (ideModeOn() === false) setIdeMode(true);
|
|
7481
|
+
/* mobil proje: npm run web + telefon silueti — kullanıcıya net geri bildirim */
|
|
7482
|
+
if (r.mobile) toast('Mobil önizleme: dev sunucu başlatıldı — telefon siluetine bak');
|
|
7094
7483
|
} else {
|
|
7095
7484
|
toast((r && r.error) || 'preview açılamadı');
|
|
7096
7485
|
}
|