beast-agent 0.20.0 → 0.22.0
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/README.md +1 -1
- package/bin/beast-agent.js +16 -6
- package/package.json +3 -2
- package/scripts/release.js +21 -34
- package/src/agent/bots.js +17 -3
- package/src/agent/engine.js +258 -57
- package/src/main.js +332 -20
- package/src/preload.js +10 -0
- package/src/renderer/i18n.js +28 -2
- package/src/renderer/index.html +46 -1
- package/src/renderer/renderer.js +855 -10
- package/src/renderer/style.css +524 -2
package/src/renderer/renderer.js
CHANGED
|
@@ -70,6 +70,21 @@ const els = {
|
|
|
70
70
|
termClear: $('#termClear'),
|
|
71
71
|
termClose: $('#termClose'),
|
|
72
72
|
termResize: $('#termResize'),
|
|
73
|
+
bcOut: $('#bcOut'),
|
|
74
|
+
bcCwd: $('#bcCwd'),
|
|
75
|
+
bcInput: $('#bcInput'),
|
|
76
|
+
bcStop: $('#bcStop'),
|
|
77
|
+
bcClear: $('#bcClear'),
|
|
78
|
+
bcNew: $('#bcNew'),
|
|
79
|
+
bcTodoWrap: $('#bcTodoWrap'),
|
|
80
|
+
codeTabs: $('#codeTabs'),
|
|
81
|
+
codeTa: $('#codeTa'),
|
|
82
|
+
codePath: $('#codePath'),
|
|
83
|
+
codeSave: $('#codeSave'),
|
|
84
|
+
ideRow: $('#ideRow'),
|
|
85
|
+
ideSplit: $('#ideSplit'),
|
|
86
|
+
bcPanel: $('#bcPanel'),
|
|
87
|
+
fileCtxMenu: $('#fileCtxMenu'),
|
|
73
88
|
cronName: $('#cronName'),
|
|
74
89
|
cronPreset: $('#cronPreset'),
|
|
75
90
|
cronSchedule: $('#cronSchedule'),
|
|
@@ -298,6 +313,15 @@ function addErrorBubble(text) {
|
|
|
298
313
|
}
|
|
299
314
|
|
|
300
315
|
/* ---------------- tool cards ---------------- */
|
|
316
|
+
/* Ard arda gelen araç kartları TEK kutuda toplanır (.tool-box): çalışırken
|
|
317
|
+
lacivert outline döner, gövde max 420px scroll'lu. Grup; tool_calls İÇEREN
|
|
318
|
+
iş turlarında açık kalır, saf metin cevapta (normal mesaj) kapanır. */
|
|
319
|
+
let chatToolGroup = null; /* { box, body } */
|
|
320
|
+
|
|
321
|
+
function closeChatToolGroup() {
|
|
322
|
+
if (chatToolGroup) chatToolGroup.box.classList.remove('running');
|
|
323
|
+
chatToolGroup = null;
|
|
324
|
+
}
|
|
301
325
|
|
|
302
326
|
function argSummary(name, args) {
|
|
303
327
|
try {
|
|
@@ -314,6 +338,18 @@ function argSummary(name, args) {
|
|
|
314
338
|
function addToolCard(callId, name, args) {
|
|
315
339
|
streamEl = null;
|
|
316
340
|
showEmpty(false);
|
|
341
|
+
/* grup yoksa / DOM temizlenmişse yeni kutu aç */
|
|
342
|
+
if (!chatToolGroup || !els.msgs.contains(chatToolGroup.box)) {
|
|
343
|
+
const box = document.createElement('div');
|
|
344
|
+
box.className = 'tool-box running';
|
|
345
|
+
const body = document.createElement('div');
|
|
346
|
+
body.className = 'tool-boxbody';
|
|
347
|
+
box.appendChild(body);
|
|
348
|
+
els.msgs.appendChild(box);
|
|
349
|
+
chatToolGroup = { box, body };
|
|
350
|
+
} else {
|
|
351
|
+
chatToolGroup.box.classList.add('running');
|
|
352
|
+
}
|
|
317
353
|
const card = document.createElement('div');
|
|
318
354
|
card.className = 'tool-card';
|
|
319
355
|
card.dataset.callId = callId || '';
|
|
@@ -327,7 +363,8 @@ function addToolCard(callId, name, args) {
|
|
|
327
363
|
card.querySelector('.tool-head').addEventListener('click', () => {
|
|
328
364
|
card.querySelector('.tool-body').classList.toggle('open');
|
|
329
365
|
});
|
|
330
|
-
|
|
366
|
+
chatToolGroup.body.appendChild(card);
|
|
367
|
+
chatToolGroup.body.scrollTop = chatToolGroup.body.scrollHeight;
|
|
331
368
|
scrollDown(true);
|
|
332
369
|
return card;
|
|
333
370
|
}
|
|
@@ -345,6 +382,10 @@ function finishToolCard(callId, ok, result) {
|
|
|
345
382
|
const body = card.querySelector('.tool-body');
|
|
346
383
|
body.textContent = String(result || '(çıktı yok)').slice(0, 4000);
|
|
347
384
|
if (String(result || '').length <= 300) body.classList.add('open');
|
|
385
|
+
/* ring burada söndürülmez — iş bitene kadar (closeChatToolGroup) döner */
|
|
386
|
+
if (chatToolGroup && chatToolGroup.body.contains(card)) {
|
|
387
|
+
chatToolGroup.body.scrollTop = chatToolGroup.body.scrollHeight;
|
|
388
|
+
}
|
|
348
389
|
}
|
|
349
390
|
|
|
350
391
|
/* ---------------- sessions sidebar ---------------- */
|
|
@@ -1678,7 +1719,8 @@ async function renderUpdatePane(autoCheck) {
|
|
|
1678
1719
|
const btnInstall = pane.querySelector('#upInstall');
|
|
1679
1720
|
if (btnInstall) btnInstall.addEventListener('click', async () => {
|
|
1680
1721
|
const r = await beast.updateInstall().catch(() => ({ ok: false, error: 'ipc' }));
|
|
1681
|
-
if (
|
|
1722
|
+
if (r.ok && r.npm) toast(_t('up_npm_started'));
|
|
1723
|
+
else if (!r.ok && r.error) toast(r.error);
|
|
1682
1724
|
});
|
|
1683
1725
|
|
|
1684
1726
|
/* indirme ilerlemesi için sekme açıkken canlı tazele — YALNIZ durum kutusu */
|
|
@@ -2183,9 +2225,44 @@ function renderBotPage() {
|
|
|
2183
2225
|
else if (tab === 'log') renderBotLog(pane, b);
|
|
2184
2226
|
else if (tab === 'watcher') renderBotWatcher(pane, b);
|
|
2185
2227
|
else if (tab === 'stats') renderBotStats(pane, b);
|
|
2228
|
+
else if (tab === 'notes') renderBotNotes(pane, b);
|
|
2186
2229
|
renderBotChats(b);
|
|
2187
2230
|
}
|
|
2188
2231
|
|
|
2232
|
+
/* --- Notlar sekmesi: oturum notları — konuşma kodu, başlık, tarih ve özet metni.
|
|
2233
|
+
Admin bot (beast) tüm oturumları görür; müşteri botu yalnız kendi oturumlarını. --- */
|
|
2234
|
+
async function renderBotNotes(pane, b) {
|
|
2235
|
+
pane.innerHTML = `<h2>${_t('notes_h2')}</h2><div class="sub">${_t('notes_sub')}</div>`;
|
|
2236
|
+
let all = [];
|
|
2237
|
+
try { all = await beast.listNotes(); } catch {}
|
|
2238
|
+
const list = (b.admin ? all : all.filter((n) => (n.botId || 'beast') === b.id))
|
|
2239
|
+
.slice()
|
|
2240
|
+
.sort((x, y) => String(y.updatedAt).localeCompare(String(x.updatedAt)));
|
|
2241
|
+
if (!list.length) {
|
|
2242
|
+
pane.insertAdjacentHTML('beforeend', `<div class="mini-empty">${_t('notes_empty')}</div>`);
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
const f = document.createElement('div');
|
|
2246
|
+
f.className = 'bot-form';
|
|
2247
|
+
for (const n of list) {
|
|
2248
|
+
const when = n.updatedAt ? new Date(n.updatedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
|
|
2249
|
+
const card = document.createElement('div');
|
|
2250
|
+
card.className = 'notes-card';
|
|
2251
|
+
card.innerHTML =
|
|
2252
|
+
`<div class="notes-head"><div class="skill-name">${escapeHtml((b.icon || '') + ' ' + (n.code || '?'))} — ${escapeHtml(n.title || '')}</div>` +
|
|
2253
|
+
`<div class="notes-meta">${escapeHtml(when)} · ${n.count || 0} ${_t('bot_stat_msgs')}</div></div>` +
|
|
2254
|
+
`<div class="notes-body">${escapeHtml(String(n.notes || '').slice(0, 4000))}</div>` +
|
|
2255
|
+
`<div style="margin-top:8px"><button class="btn ghost notes-del">${_t('notes_del')}</button></div>`;
|
|
2256
|
+
card.querySelector('.notes-del').addEventListener('click', async () => {
|
|
2257
|
+
await beast.clearNotes(n.id).catch(() => {});
|
|
2258
|
+
toast(_t('deleted'));
|
|
2259
|
+
renderBotNotes(pane, b);
|
|
2260
|
+
});
|
|
2261
|
+
f.appendChild(card);
|
|
2262
|
+
}
|
|
2263
|
+
pane.appendChild(f);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2189
2266
|
function renderBotOverview(pane) {
|
|
2190
2267
|
pane.innerHTML = `<h2>${_t('bot_overview')}</h2><div class="sub">${_t('bot_overview_sub')}</div>`;
|
|
2191
2268
|
const totals = { numbers: 0, sessions: 0, msgs: 0 };
|
|
@@ -2272,6 +2349,12 @@ function renderBotSettings(pane, b) {
|
|
|
2272
2349
|
const skillChecks = BOT_SKILLS
|
|
2273
2350
|
.map(([k, lbl]) => `<label><input type="checkbox" data-skill="${k}" ${(b.skills || {})[k] ? 'checked' : ''}/> ${lbl}</label>`)
|
|
2274
2351
|
.join('');
|
|
2352
|
+
/* Yetki: 'all' tek başına tüm araçları verir (özel); web/read/chat çoklu seçilebilir */
|
|
2353
|
+
const PERM_OPTS = [['all', _t('wa_perm_all')], ['web', 'Web'], ['read', _t('wa_perm_read')], ['chat', _t('wa_perm_chat')]];
|
|
2354
|
+
const curPerms = Array.isArray(b.perm) ? b.perm : [b.perm || 'all'];
|
|
2355
|
+
const permChecks = PERM_OPTS
|
|
2356
|
+
.map(([k, lbl]) => `<label><input type="checkbox" data-perm="${k}" ${curPerms.includes(k) ? 'checked' : ''}/> ${lbl}</label>`)
|
|
2357
|
+
.join('');
|
|
2275
2358
|
const numRows = (b.numbers || [])
|
|
2276
2359
|
.map((n) => `<div class="bot-num-row" data-num="${n.num}"><span class="n">+${escapeHtml(n.num)}${n.name ? ' · ' + escapeHtml(n.name) : ''}</span><span class="x" title="${_t('bot_num_del')}">×</span></div>`)
|
|
2277
2360
|
.join('');
|
|
@@ -2293,12 +2376,8 @@ function renderBotSettings(pane, b) {
|
|
|
2293
2376
|
<div class="bot-checks" id="bSee">${seeChecks}</div>
|
|
2294
2377
|
${b.admin ? '' : `
|
|
2295
2378
|
<label class="mem-label">${_t('bot_perm')}</label>
|
|
2296
|
-
<
|
|
2297
|
-
|
|
2298
|
-
<option value="web" ${b.perm === 'web' ? 'selected' : ''}>web</option>
|
|
2299
|
-
<option value="read" ${b.perm === 'read' ? 'selected' : ''}>${_t('wa_perm_read')}</option>
|
|
2300
|
-
<option value="chat" ${b.perm === 'chat' ? 'selected' : ''}>${_t('wa_perm_chat')}</option>
|
|
2301
|
-
</select>
|
|
2379
|
+
<div class="bot-checks" id="bPerm">${permChecks}</div>
|
|
2380
|
+
<div class="sub" style="margin-top:4px">${_t('bot_perm_note')}</div>
|
|
2302
2381
|
<label class="mem-label">${_t('bot_skills')}</label>
|
|
2303
2382
|
<div class="bot-checks" id="bSkills">${skillChecks}</div>`}
|
|
2304
2383
|
<label class="mem-label">${_t('bot_browser')}</label>
|
|
@@ -2346,6 +2425,18 @@ function renderBotSettings(pane, b) {
|
|
|
2346
2425
|
if (bb) renderBotSettings(pane, bb);
|
|
2347
2426
|
})
|
|
2348
2427
|
);
|
|
2428
|
+
/* 'all' hariç diğer izinler çoklu seçilebilir; 'all' işaretlenince diğerleri kapanır */
|
|
2429
|
+
f.querySelectorAll('#bPerm input').forEach((c) =>
|
|
2430
|
+
c.addEventListener('change', () => {
|
|
2431
|
+
if (c.dataset.perm === 'all' && c.checked) {
|
|
2432
|
+
f.querySelectorAll('#bPerm input').forEach((x) => { if (x !== c) x.checked = false; });
|
|
2433
|
+
} else if (c.checked) {
|
|
2434
|
+
const all = f.querySelector('#bPerm input[data-perm="all"]');
|
|
2435
|
+
if (all) all.checked = false;
|
|
2436
|
+
}
|
|
2437
|
+
if (![...f.querySelectorAll('#bPerm input')].some((x) => x.checked)) c.checked = true; // en az biri seçili kalsın
|
|
2438
|
+
})
|
|
2439
|
+
);
|
|
2349
2440
|
$('#bSave').addEventListener('click', async () => {
|
|
2350
2441
|
const patch = {
|
|
2351
2442
|
name: $('#bName').value.trim(),
|
|
@@ -2358,7 +2449,8 @@ function renderBotSettings(pane, b) {
|
|
|
2358
2449
|
numbers: (b.numbers || []).map((n) => n.num),
|
|
2359
2450
|
};
|
|
2360
2451
|
if (!b.admin) {
|
|
2361
|
-
|
|
2452
|
+
const selPerms = [...f.querySelectorAll('#bPerm input:checked')].map((c) => c.dataset.perm);
|
|
2453
|
+
patch.perm = selPerms.includes('all') ? 'all' : selPerms;
|
|
2362
2454
|
const sk = {};
|
|
2363
2455
|
f.querySelectorAll('#bSkills input').forEach((c) => { sk[c.dataset.skill] = c.checked; });
|
|
2364
2456
|
patch.skills = sk;
|
|
@@ -2784,6 +2876,12 @@ function renderAgentsPane() {
|
|
|
2784
2876
|
function onEvent(ev) {
|
|
2785
2877
|
if (ev.type === 'sessions') { refreshSessions(); return; }
|
|
2786
2878
|
if (ev.type === 'approval') {
|
|
2879
|
+
/* Beast Code oturumunun onayı: panelde ipucu + kart sohbete düşer */
|
|
2880
|
+
if (bcSessionId && ev.sessionId === bcSessionId) {
|
|
2881
|
+
showApprovalCard(ev);
|
|
2882
|
+
bcLine('t-dim', '⏳ onay bekleniyor: ' + (ev.tool || '?') + ' — IDE modundan çıkıp sohbetteki kartı onayla');
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2787
2885
|
if (ev.sessionId && ev.sessionId !== activeId) return;
|
|
2788
2886
|
showApprovalCard(ev);
|
|
2789
2887
|
return;
|
|
@@ -2800,6 +2898,13 @@ function onEvent(ev) {
|
|
|
2800
2898
|
scheduleAgentsRender();
|
|
2801
2899
|
return;
|
|
2802
2900
|
}
|
|
2901
|
+
/* Beast Code oturumu (IDE modu ortasındaki panel): olayları panele akıt,
|
|
2902
|
+
ana sohbeti kirletme */
|
|
2903
|
+
if (bcSessionId && ev.sessionId === bcSessionId) {
|
|
2904
|
+
bcIngest(ev);
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2803
2908
|
if (ev.sessionId && ev.sessionId !== activeId) {
|
|
2804
2909
|
/* paralel ajan canlı akışı: sohbeti kirletme, sekmede göster */
|
|
2805
2910
|
if (agentState.runningIds.has(ev.sessionId)) {
|
|
@@ -2816,6 +2921,7 @@ function onEvent(ev) {
|
|
|
2816
2921
|
els.msgs.innerHTML = '';
|
|
2817
2922
|
streamEl = null;
|
|
2818
2923
|
streamRaw = '';
|
|
2924
|
+
closeChatToolGroup();
|
|
2819
2925
|
showEmpty(true);
|
|
2820
2926
|
refreshSessions();
|
|
2821
2927
|
break;
|
|
@@ -2825,7 +2931,12 @@ function onEvent(ev) {
|
|
|
2825
2931
|
break;
|
|
2826
2932
|
case 'message':
|
|
2827
2933
|
if (ev.message.role === 'user') addUserBubble(ev.message.content);
|
|
2828
|
-
else if (ev.message.role === 'assistant')
|
|
2934
|
+
else if (ev.message.role === 'assistant') {
|
|
2935
|
+
finalizeAssistant(ev.message.content);
|
|
2936
|
+
/* tool_calls'lı iş turu grupları açık tutar; saf metin (normal cevap) kapatır */
|
|
2937
|
+
const hasTools = Array.isArray(ev.message.tool_calls) && ev.message.tool_calls.length > 0;
|
|
2938
|
+
if (!hasTools) closeChatToolGroup();
|
|
2939
|
+
}
|
|
2829
2940
|
break;
|
|
2830
2941
|
case 'token':
|
|
2831
2942
|
ensureStreamBubble();
|
|
@@ -2869,6 +2980,9 @@ function onEvent(ev) {
|
|
|
2869
2980
|
} else {
|
|
2870
2981
|
toggleRail(!railPrefBeforeBrowser);
|
|
2871
2982
|
}
|
|
2983
|
+
/* IDE modunda tarayıcı açılır/kapanır/genişlik değişirse row yeniden bölünür —
|
|
2984
|
+
editör + Beast Code ORTAK kırpılıp ORTAK açılır (kullanıcı payı korunur) */
|
|
2985
|
+
if (ideModeOn()) ideSplitApplyFrac();
|
|
2872
2986
|
if (shown) {
|
|
2873
2987
|
els.browserBtn.classList.add('on');
|
|
2874
2988
|
if (ev.url && document.activeElement !== els.bbUrl) {
|
|
@@ -2894,11 +3008,13 @@ function onEvent(ev) {
|
|
|
2894
3008
|
applyState();
|
|
2895
3009
|
break;
|
|
2896
3010
|
case 'done':
|
|
3011
|
+
closeChatToolGroup();
|
|
2897
3012
|
setBusy(false);
|
|
2898
3013
|
refreshSessions();
|
|
2899
3014
|
els.input.focus();
|
|
2900
3015
|
break;
|
|
2901
3016
|
case 'error':
|
|
3017
|
+
closeChatToolGroup();
|
|
2902
3018
|
setBusy(false);
|
|
2903
3019
|
addErrorBubble(ev.error);
|
|
2904
3020
|
refreshSessions();
|
|
@@ -4315,3 +4431,732 @@ document.querySelectorAll('.stab').forEach((b) =>
|
|
|
4315
4431
|
renderStorePane();
|
|
4316
4432
|
})
|
|
4317
4433
|
);
|
|
4434
|
+
|
|
4435
|
+
|
|
4436
|
+
/* ================= IDE MODU =================
|
|
4437
|
+
Tek tuş (#ideBtn): sol → dosya gezgini, orta → chat, sağ → dahili tarayıcı (preview).
|
|
4438
|
+
Preview: workspace kökündeki index.html (yoksa ilk *.html) sağdaki tarayıcıda açılır.
|
|
4439
|
+
Dosyaya tıkla → editör; klasöre tıkla → aç/kapa. */
|
|
4440
|
+
|
|
4441
|
+
const IDE_ICONS = {
|
|
4442
|
+
'.html': '🌐', '.htm': '🌐', '.css': '🎨', '.js': '📜', '.mjs': '📜', '.cjs': '📜',
|
|
4443
|
+
'.ts': '📜', '.jsx': '📜', '.tsx': '📜', '.json': '🧾', '.md': '📝', '.txt': '📄',
|
|
4444
|
+
'.py': '🐍', '.ps1': '⚙️', '.bat': '⚙️', '.cmd': '⚙️', '.sh': '⚙️', '.yaml': '🧾',
|
|
4445
|
+
'.yml': '🧾', '.png': '🖼️', '.jpg': '🖼️', '.jpeg': '🖼️', '.gif': '🖼️', '.svg': '🖼️',
|
|
4446
|
+
'.pdf': '📕',
|
|
4447
|
+
};
|
|
4448
|
+
|
|
4449
|
+
const ideState = {
|
|
4450
|
+
cache: new Map(), // rel -> entries[]
|
|
4451
|
+
open: new Set(['']),
|
|
4452
|
+
path: '',
|
|
4453
|
+
};
|
|
4454
|
+
|
|
4455
|
+
function ideModeOn() {
|
|
4456
|
+
return document.body.classList.contains('ide-mode');
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
async function setIdeMode(on) {
|
|
4460
|
+
document.body.classList.toggle('ide-mode', !!on);
|
|
4461
|
+
/* IDE modu oturumluk: uygulama HER AÇILIŞTA agent (chat) modunda başlar */
|
|
4462
|
+
toggleRail(!!on);
|
|
4463
|
+
/* IDE'den çıkış: açık tarayıcı da kapansın — her şey default haline dönsün */
|
|
4464
|
+
if (!on && document.body.classList.contains('browser-open')) {
|
|
4465
|
+
try { beast.toggleBrowser(); } catch {}
|
|
4466
|
+
}
|
|
4467
|
+
if (on) {
|
|
4468
|
+
ideSplitRestore();
|
|
4469
|
+
await loadIdeTree();
|
|
4470
|
+
bcBanner();
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
|
|
4474
|
+
/* ---------- editor/chat ayırıcı (VS Code gibi sürükle-bırak) ----------
|
|
4475
|
+
Orantılı model: kaydedilen, Beast Code'un ideRow içindeki PAYI'dır (0-1).
|
|
4476
|
+
Tarayıcı açılınca/kapanınca/genişlik değişince row yeniden bölünür — editör
|
|
4477
|
+
ve Beast Code ORTAK kırpılıp ORTAK açılır (oranlar korunur).
|
|
4478
|
+
Kayıt yoksa default: tarayıcı açıkken 3 eşit parça, kapalıyken 2 eşit parça. */
|
|
4479
|
+
let ideSplitFrac = 0; /* 0 = kayıt yok → 0.5 default */
|
|
4480
|
+
|
|
4481
|
+
function ideSplitApplyFrac() {
|
|
4482
|
+
if (!ideSplitFrac) ideSplitFrac = 0.5;
|
|
4483
|
+
ideSplitFrac = Math.max(0.2, Math.min(ideSplitFrac, 0.8));
|
|
4484
|
+
const rowW = els.ideRow && els.ideRow.clientWidth ? els.ideRow.clientWidth : window.innerWidth - 250;
|
|
4485
|
+
const w = Math.max(280, Math.min(Math.round(rowW * ideSplitFrac), Math.max(320, rowW - 300)));
|
|
4486
|
+
document.body.style.setProperty('--ideSplit', w + 'px');
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
function ideSplitSave() {
|
|
4490
|
+
try { localStorage.setItem('beast.ideSplit', String(ideSplitFrac)); } catch {}
|
|
4491
|
+
}
|
|
4492
|
+
|
|
4493
|
+
function ideSplitRestore() {
|
|
4494
|
+
let saved = 0;
|
|
4495
|
+
try { saved = parseFloat(localStorage.getItem('beast.ideSplit')) || 0; } catch {}
|
|
4496
|
+
const hasUserSetting = saved > 0.05 && saved < 0.95;
|
|
4497
|
+
ideSplitFrac = hasUserSetting ? saved : 0;
|
|
4498
|
+
ideSplitApplyFrac();
|
|
4499
|
+
/* tarayıcı açıksa ve kullanıcı hiç ayar yapmadıysa üç parçayı eşitle */
|
|
4500
|
+
const browserOpen = document.body.classList.contains('browser-open');
|
|
4501
|
+
if (browserOpen && !hasUserSetting) {
|
|
4502
|
+
beast.browserSetWidth(Math.round((window.innerWidth - 250) / 3)).catch(() => {});
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
|
|
4506
|
+
if (els.ideSplit) {
|
|
4507
|
+
els.ideSplit.addEventListener('mousedown', (e) => {
|
|
4508
|
+
e.preventDefault();
|
|
4509
|
+
els.ideSplit.classList.add('dragging');
|
|
4510
|
+
/* tarayıcı dock'u drag sırasında sabit — sağ kenar referansı mousedown anında */
|
|
4511
|
+
const rightEdge = els.bcPanel ? els.bcPanel.getBoundingClientRect().right : window.innerWidth;
|
|
4512
|
+
const rowW = Math.max(600, els.ideRow && els.ideRow.clientWidth ? els.ideRow.clientWidth : window.innerWidth - 250);
|
|
4513
|
+
const move = (ev) => {
|
|
4514
|
+
const w = Math.max(280, Math.min(rightEdge - ev.clientX, rowW - 300));
|
|
4515
|
+
ideSplitFrac = Math.max(0.2, Math.min(w / rowW, 0.8));
|
|
4516
|
+
document.body.style.setProperty('--ideSplit', Math.round(w) + 'px');
|
|
4517
|
+
};
|
|
4518
|
+
const up = () => {
|
|
4519
|
+
document.removeEventListener('mousemove', move);
|
|
4520
|
+
document.removeEventListener('mouseup', up);
|
|
4521
|
+
els.ideSplit.classList.remove('dragging');
|
|
4522
|
+
ideSplitSave();
|
|
4523
|
+
};
|
|
4524
|
+
document.addEventListener('mousemove', move);
|
|
4525
|
+
document.addEventListener('mouseup', up);
|
|
4526
|
+
});
|
|
4527
|
+
window.addEventListener('resize', () => {
|
|
4528
|
+
if (ideModeOn()) ideSplitApplyFrac();
|
|
4529
|
+
});
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
async function loadIdeTree() {
|
|
4533
|
+
ideState.cache.clear();
|
|
4534
|
+
await renderFileTree();
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
function fmtFileSize(n) {
|
|
4538
|
+
n = Number(n) || 0;
|
|
4539
|
+
if (n >= 1048576) return (n / 1048576).toFixed(1) + 'M';
|
|
4540
|
+
if (n >= 1024) return Math.round(n / 1024) + 'K';
|
|
4541
|
+
return n ? n + 'B' : '';
|
|
4542
|
+
}
|
|
4543
|
+
|
|
4544
|
+
async function renderFileTree() {
|
|
4545
|
+
const tree = $('#fileTree');
|
|
4546
|
+
if (!tree) return;
|
|
4547
|
+
tree.innerHTML = '';
|
|
4548
|
+
const wsLabel = $('#filePanelPath');
|
|
4549
|
+
const build = async (rel, depth, container) => {
|
|
4550
|
+
let entries = ideState.cache.get(rel);
|
|
4551
|
+
if (!entries) {
|
|
4552
|
+
const r = await beast.ideTree(rel).catch(() => null);
|
|
4553
|
+
if (!r || !r.ok) {
|
|
4554
|
+
if (!rel) container.innerHTML = `<div class="file-empty">${escapeHtml((r && r.error) || 'okunamadı')}</div>`;
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
entries = r.entries;
|
|
4558
|
+
ideState.cache.set(rel, entries);
|
|
4559
|
+
if (r.workspace) {
|
|
4560
|
+
ideState.path = r.workspace;
|
|
4561
|
+
if (wsLabel) { wsLabel.textContent = r.workspace; wsLabel.title = r.workspace; }
|
|
4562
|
+
if (els.bcCwd) { els.bcCwd.textContent = r.workspace; els.bcCwd.title = r.workspace; }
|
|
4563
|
+
bcOnFolderChanged(r.workspace);
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
for (const e of entries) {
|
|
4567
|
+
const child = rel ? rel + '/' + e.name : e.name;
|
|
4568
|
+
const row = document.createElement('div');
|
|
4569
|
+
row.className = 'file-row';
|
|
4570
|
+
row.style.paddingLeft = 6 + depth * 14 + 'px';
|
|
4571
|
+
const ico = e.dir ? (ideState.open.has(child) ? '📂' : '📁') : IDE_ICONS[e.name.slice(e.name.lastIndexOf('.')).toLowerCase()] || '📄';
|
|
4572
|
+
row.innerHTML =
|
|
4573
|
+
`<span class="f-ico">${ico}</span>` +
|
|
4574
|
+
`<span class="f-nm" title="${escapeHtml(e.name)}">${escapeHtml(e.name)}</span>` +
|
|
4575
|
+
(e.dir ? '' : `<span class="f-sz">${fmtFileSize(e.size)}</span>`);
|
|
4576
|
+
row.addEventListener('click', () => {
|
|
4577
|
+
if (e.dir) {
|
|
4578
|
+
if (ideState.open.has(child)) ideState.open.delete(child);
|
|
4579
|
+
else ideState.open.add(child);
|
|
4580
|
+
renderFileTree();
|
|
4581
|
+
} else {
|
|
4582
|
+
codeOpen(child);
|
|
4583
|
+
}
|
|
4584
|
+
});
|
|
4585
|
+
row.dataset.rel = child;
|
|
4586
|
+
row.dataset.dir = e.dir ? '1' : '0';
|
|
4587
|
+
container.appendChild(row);
|
|
4588
|
+
if (e.dir && ideState.open.has(child)) {
|
|
4589
|
+
const box = document.createElement('div');
|
|
4590
|
+
container.appendChild(box);
|
|
4591
|
+
await build(child, depth + 1, box);
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
};
|
|
4595
|
+
await build('', 0, tree);
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
/* ---------- sekmeli kod editörü (VS Code düzeni) ----------
|
|
4599
|
+
Dosya tıklaması MODAL değil, codePane içinde SEKME açar.
|
|
4600
|
+
Yanında Beast Code chat'i, onun sağındaki preview tarayıcı durur. */
|
|
4601
|
+
|
|
4602
|
+
const codeTabs = []; /* { rel, content, dirty } */
|
|
4603
|
+
let codeActive = -1;
|
|
4604
|
+
|
|
4605
|
+
function codeRenderTabs() {
|
|
4606
|
+
const bar = els.codeTabs;
|
|
4607
|
+
if (!bar) return;
|
|
4608
|
+
bar.innerHTML = '';
|
|
4609
|
+
codeTabs.forEach((t, i) => {
|
|
4610
|
+
const el = document.createElement('div');
|
|
4611
|
+
el.className = 'code-tab' + (i === codeActive ? ' active' : '') + (t.dirty ? ' dirty' : '');
|
|
4612
|
+
el.title = t.rel;
|
|
4613
|
+
el.innerHTML =
|
|
4614
|
+
'<span class="code-tab-name"></span>' +
|
|
4615
|
+
'<button class="code-tab-x" title="Sekmeyi kapat">\u00D7</button>';
|
|
4616
|
+
el.querySelector('.code-tab-name').textContent = t.rel;
|
|
4617
|
+
el.addEventListener('click', () => codeActivate(i));
|
|
4618
|
+
el.querySelector('.code-tab-x').addEventListener('click', (e) => {
|
|
4619
|
+
e.stopPropagation();
|
|
4620
|
+
codeClose(i);
|
|
4621
|
+
});
|
|
4622
|
+
bar.appendChild(el);
|
|
4623
|
+
});
|
|
4624
|
+
}
|
|
4625
|
+
|
|
4626
|
+
function codeFlushActive() {
|
|
4627
|
+
if (codeActive >= 0 && codeTabs[codeActive]) {
|
|
4628
|
+
codeTabs[codeActive].content = els.codeTa.value;
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
function codeActivate(i) {
|
|
4633
|
+
codeFlushActive();
|
|
4634
|
+
if (i < 0 || i >= codeTabs.length) {
|
|
4635
|
+
codeActive = -1;
|
|
4636
|
+
els.codeTa.value = '';
|
|
4637
|
+
els.codePath.textContent = '\u2014';
|
|
4638
|
+
codeRenderTabs();
|
|
4639
|
+
return;
|
|
4640
|
+
}
|
|
4641
|
+
codeActive = i;
|
|
4642
|
+
const t = codeTabs[i];
|
|
4643
|
+
els.codeTa.value = t.content;
|
|
4644
|
+
els.codePath.textContent = t.rel;
|
|
4645
|
+
els.codePath.title = (ideState.path || '') + '\\' + t.rel.replace(/\//g, '\\');
|
|
4646
|
+
codeRenderTabs();
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
async function codeOpen(rel) {
|
|
4650
|
+
const idx = codeTabs.findIndex((t) => t.rel === rel);
|
|
4651
|
+
if (idx >= 0) {
|
|
4652
|
+
codeActivate(idx);
|
|
4653
|
+
return;
|
|
4654
|
+
}
|
|
4655
|
+
const r = await beast.ideRead(rel).catch(() => null);
|
|
4656
|
+
if (!r || !r.ok) {
|
|
4657
|
+
toast((r && r.error) || 'okunamad\u0131');
|
|
4658
|
+
return;
|
|
4659
|
+
}
|
|
4660
|
+
codeTabs.push({ rel, content: r.content || '', dirty: false });
|
|
4661
|
+
codeActivate(codeTabs.length - 1);
|
|
4662
|
+
els.codeTa.focus();
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4665
|
+
function codeClose(i) {
|
|
4666
|
+
if (i < 0 || i >= codeTabs.length) return;
|
|
4667
|
+
codeFlushActive();
|
|
4668
|
+
const oldActive = codeActive;
|
|
4669
|
+
const wasActive = i === oldActive;
|
|
4670
|
+
codeTabs.splice(i, 1);
|
|
4671
|
+
if (codeTabs.length === 0) {
|
|
4672
|
+
codeActive = -1;
|
|
4673
|
+
els.codeTa.value = '';
|
|
4674
|
+
els.codePath.textContent = '\u2014';
|
|
4675
|
+
codeRenderTabs();
|
|
4676
|
+
return;
|
|
4677
|
+
}
|
|
4678
|
+
let next = oldActive;
|
|
4679
|
+
if (wasActive) next = Math.min(i, codeTabs.length - 1);
|
|
4680
|
+
else if (i < oldActive) next = oldActive - 1;
|
|
4681
|
+
codeActive = -1; /* activate içindeki flush, eski içerikle yeni sekmeyi ezmesin */
|
|
4682
|
+
codeActivate(next);
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
async function codeSave() {
|
|
4686
|
+
if (codeActive < 0 || !codeTabs[codeActive]) return;
|
|
4687
|
+
codeFlushActive();
|
|
4688
|
+
const t = codeTabs[codeActive];
|
|
4689
|
+
const r = await beast.ideWrite(t.rel, t.content).catch(() => null);
|
|
4690
|
+
if (r && r.ok) {
|
|
4691
|
+
t.dirty = false;
|
|
4692
|
+
codeRenderTabs();
|
|
4693
|
+
toast(_t('ide_saved'));
|
|
4694
|
+
} else {
|
|
4695
|
+
toast((r && r.error) || 'kaydedilemedi');
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
|
|
4699
|
+
$('#ideBtn').addEventListener('click', () => setIdeMode(!ideModeOn()));
|
|
4700
|
+
$('#filePick').addEventListener('click', async () => {
|
|
4701
|
+
const r = await beast.ideSetRoot().catch(() => null);
|
|
4702
|
+
if (r && r.ok) {
|
|
4703
|
+
ideState.open = new Set(['']);
|
|
4704
|
+
await loadIdeTree();
|
|
4705
|
+
} else if (r && !r.canceled && r.error) {
|
|
4706
|
+
toast(r.error);
|
|
4707
|
+
}
|
|
4708
|
+
});
|
|
4709
|
+
$('#fileRefresh').addEventListener('click', () => loadIdeTree());
|
|
4710
|
+
$('#filePreview').addEventListener('click', async () => {
|
|
4711
|
+
const r = await beast.idePreview().catch(() => null);
|
|
4712
|
+
if (r && r.ok) {
|
|
4713
|
+
if (ideModeOn() === false) setIdeMode(true);
|
|
4714
|
+
} else {
|
|
4715
|
+
toast((r && r.error) || 'preview açılamadı');
|
|
4716
|
+
}
|
|
4717
|
+
});
|
|
4718
|
+
if (els.codeSave) els.codeSave.addEventListener('click', codeSave);
|
|
4719
|
+
if (els.codeTa) {
|
|
4720
|
+
els.codeTa.addEventListener('input', () => {
|
|
4721
|
+
if (codeActive < 0) return;
|
|
4722
|
+
codeTabs[codeActive].dirty = true;
|
|
4723
|
+
const el = els.codeTabs.children[codeActive];
|
|
4724
|
+
if (el) el.classList.add('dirty');
|
|
4725
|
+
});
|
|
4726
|
+
els.codeTa.addEventListener('keydown', (e) => {
|
|
4727
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
|
|
4728
|
+
e.preventDefault();
|
|
4729
|
+
codeSave();
|
|
4730
|
+
}
|
|
4731
|
+
});
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4734
|
+
/* ---------- dosya ağacı sağ tık menüsü (Sil / Aç / Preview) ---------- */
|
|
4735
|
+
|
|
4736
|
+
function fileCtxHide() {
|
|
4737
|
+
if (els.fileCtxMenu) els.fileCtxMenu.hidden = true;
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4740
|
+
function fileCtxShow(e, rel, isDir) {
|
|
4741
|
+
const menu = els.fileCtxMenu;
|
|
4742
|
+
if (!menu || !rel) return;
|
|
4743
|
+
e.preventDefault();
|
|
4744
|
+
const isHtml = /\.html?$/i.test(rel);
|
|
4745
|
+
const items = [];
|
|
4746
|
+
if (isDir) {
|
|
4747
|
+
items.push({
|
|
4748
|
+
label: ideState.open.has(rel) ? 'Kapat' : 'Aç',
|
|
4749
|
+
fn: () => {
|
|
4750
|
+
if (ideState.open.has(rel)) ideState.open.delete(rel);
|
|
4751
|
+
else ideState.open.add(rel);
|
|
4752
|
+
renderFileTree();
|
|
4753
|
+
},
|
|
4754
|
+
});
|
|
4755
|
+
} else {
|
|
4756
|
+
items.push({ label: 'Aç', fn: () => codeOpen(rel) });
|
|
4757
|
+
if (isHtml) {
|
|
4758
|
+
items.push({
|
|
4759
|
+
label: 'Preview',
|
|
4760
|
+
fn: async () => {
|
|
4761
|
+
const r = await beast.idePreviewFile(rel).catch(() => null);
|
|
4762
|
+
if (!r || !r.ok) toast((r && r.error) || 'preview açılamadı');
|
|
4763
|
+
},
|
|
4764
|
+
});
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
items.push({
|
|
4768
|
+
label: 'Sil',
|
|
4769
|
+
danger: true,
|
|
4770
|
+
fn: async () => {
|
|
4771
|
+
const r = await beast.ideDelete(rel).catch(() => null);
|
|
4772
|
+
if (r && r.ok) {
|
|
4773
|
+
/* silinen dosya/klasörün açık sekmelerini kapat */
|
|
4774
|
+
for (let i = codeTabs.length - 1; i >= 0; i--) {
|
|
4775
|
+
if (codeTabs[i].rel === rel || codeTabs[i].rel.startsWith(rel + '/')) codeClose(i);
|
|
4776
|
+
}
|
|
4777
|
+
await loadIdeTree();
|
|
4778
|
+
} else if (r && !r.canceled && r.error) {
|
|
4779
|
+
toast(r.error);
|
|
4780
|
+
}
|
|
4781
|
+
},
|
|
4782
|
+
});
|
|
4783
|
+
|
|
4784
|
+
menu.innerHTML = '';
|
|
4785
|
+
for (const it of items) {
|
|
4786
|
+
const el = document.createElement('div');
|
|
4787
|
+
el.className = 'ctx-item' + (it.danger ? ' danger' : '');
|
|
4788
|
+
el.textContent = it.label;
|
|
4789
|
+
el.addEventListener('click', () => {
|
|
4790
|
+
fileCtxHide();
|
|
4791
|
+
it.fn();
|
|
4792
|
+
});
|
|
4793
|
+
menu.appendChild(el);
|
|
4794
|
+
}
|
|
4795
|
+
menu.hidden = false;
|
|
4796
|
+
const mh = items.length * 33 + 8;
|
|
4797
|
+
menu.style.left = Math.max(4, Math.min(e.clientX, window.innerWidth - 170)) + 'px';
|
|
4798
|
+
menu.style.top = Math.max(4, Math.min(e.clientY, window.innerHeight - mh)) + 'px';
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4801
|
+
if (els.fileCtxMenu) {
|
|
4802
|
+
document.addEventListener('click', fileCtxHide);
|
|
4803
|
+
window.addEventListener('blur', fileCtxHide);
|
|
4804
|
+
window.addEventListener('keydown', (e) => {
|
|
4805
|
+
if (e.key === 'Escape') fileCtxHide();
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4808
|
+
$('#fileTree').addEventListener('contextmenu', (e) => {
|
|
4809
|
+
const row = e.target.closest('.file-row');
|
|
4810
|
+
if (!row || !row.dataset.rel) {
|
|
4811
|
+
fileCtxHide();
|
|
4812
|
+
return;
|
|
4813
|
+
}
|
|
4814
|
+
fileCtxShow(e, row.dataset.rel, row.dataset.dir === '1');
|
|
4815
|
+
});
|
|
4816
|
+
|
|
4817
|
+
/* ---------- IDE modu ortası: BEAST CODE paneli ----------
|
|
4818
|
+
Sohbet yerine Beast'in kendi ajanı (varsayılan model) çalışır;
|
|
4819
|
+
soldaki dosya panelindeki klasörü workspace olarak kullanır.
|
|
4820
|
+
Gizli engine oturumu → olaylar onEvent içinde panele yönlendirilir. */
|
|
4821
|
+
|
|
4822
|
+
const BC_MAX_LINES = 1200;
|
|
4823
|
+
let bcSessionId = null;
|
|
4824
|
+
let bcRunning = false;
|
|
4825
|
+
let bcBannerDone = false;
|
|
4826
|
+
let bcStreamEl = null;
|
|
4827
|
+
let bcStreamRaw = '';
|
|
4828
|
+
|
|
4829
|
+
function bcScroll(force) {
|
|
4830
|
+
const b = els.bcOut;
|
|
4831
|
+
if (!b) return;
|
|
4832
|
+
/* her yeni içerikte son mesaja doğru otomatik kaydır */
|
|
4833
|
+
b.scrollTop = b.scrollHeight;
|
|
4834
|
+
}
|
|
4835
|
+
|
|
4836
|
+
/* kutu içi gövdeyi (bc-toolbody) de son bölüme kaydır */
|
|
4837
|
+
function bcBodyScroll(el) {
|
|
4838
|
+
const b = el && el.closest ? el.closest('.bc-toolbody') : null;
|
|
4839
|
+
if (b) b.scrollTop = b.scrollHeight;
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
function bcLine(cls, text, time = false) {
|
|
4843
|
+
if (!els.bcOut) return;
|
|
4844
|
+
const el = document.createElement('div');
|
|
4845
|
+
el.className = 't-line ' + (cls || '');
|
|
4846
|
+
const ts = time ? '<span class="t-time">' + new Date().toTimeString().slice(0, 8) + '</span> ' : '';
|
|
4847
|
+
el.innerHTML = ts + escapeHtml(String(text ?? ''));
|
|
4848
|
+
els.bcOut.appendChild(el);
|
|
4849
|
+
while (els.bcOut.childElementCount > BC_MAX_LINES) els.bcOut.removeChild(els.bcOut.firstChild);
|
|
4850
|
+
bcScroll();
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4853
|
+
function bcBanner() {
|
|
4854
|
+
if (bcBannerDone) return;
|
|
4855
|
+
bcBannerDone = true;
|
|
4856
|
+
const cwd = ideState.path || '';
|
|
4857
|
+
if (els.bcCwd && cwd) { els.bcCwd.textContent = cwd; els.bcCwd.title = cwd; }
|
|
4858
|
+
bcLine('t-sys', 'BEAST CODE — çalışma klasörü: ' + (cwd || '?'));
|
|
4859
|
+
bcLine('t-dim', 'Beast\u2019in varsayılan modeliyle kod yazar; yazışma bu klasöre bağlıdır. + yeni oturum · Temizle çıktıyı siler.', false);
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4862
|
+
/* Klasör değişimi (Klasör seç / kök değişti) → panel taze başlar;
|
|
4863
|
+
oturumlar main tarafında klasör bazlı tutulur (klasöre dönüşte aynı sohbet sürer).
|
|
4864
|
+
bcSessionId kasıtlı sıfırlanmaz: klasör değişse bile çalışan işin done/idle
|
|
4865
|
+
sinyali panele ulaşıp kilidi açar. */
|
|
4866
|
+
let bcWsPath = '';
|
|
4867
|
+
|
|
4868
|
+
function bcOnFolderChanged(p) {
|
|
4869
|
+
if (!p || bcWsPath === p) return;
|
|
4870
|
+
const first = !bcWsPath;
|
|
4871
|
+
bcWsPath = p;
|
|
4872
|
+
if (first) return; /* ilk yükleme — panel zaten boş, banner setIdeMode'da basılır */
|
|
4873
|
+
if (els.bcOut) els.bcOut.innerHTML = '';
|
|
4874
|
+
bcTools.clear();
|
|
4875
|
+
bcCloseToolGroup();
|
|
4876
|
+
bcHideTodos();
|
|
4877
|
+
bcStreamEl = null;
|
|
4878
|
+
bcStreamRaw = '';
|
|
4879
|
+
bcBannerDone = false;
|
|
4880
|
+
bcBanner();
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4883
|
+
function bcSetBusy(v) {
|
|
4884
|
+
bcRunning = !!v;
|
|
4885
|
+
if (els.bcStop) els.bcStop.hidden = !v;
|
|
4886
|
+
}
|
|
4887
|
+
|
|
4888
|
+
function bcFlushStream() {
|
|
4889
|
+
bcStreamEl = null;
|
|
4890
|
+
bcStreamRaw = '';
|
|
4891
|
+
}
|
|
4892
|
+
|
|
4893
|
+
function bcStreamDelta(delta) {
|
|
4894
|
+
if (!bcStreamEl) {
|
|
4895
|
+
bcStreamEl = document.createElement('div');
|
|
4896
|
+
bcStreamEl.className = 't-line t-out';
|
|
4897
|
+
els.bcOut.appendChild(bcStreamEl);
|
|
4898
|
+
}
|
|
4899
|
+
bcStreamRaw += String(delta || '');
|
|
4900
|
+
bcStreamEl.textContent = bcStreamRaw.slice(-4000);
|
|
4901
|
+
bcScroll(true);
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4904
|
+
/* İş bitti → preview açık ve IDE modundaysa workspace sayfasını otomatik yenile */
|
|
4905
|
+
let bcPrevAt = 0;
|
|
4906
|
+
|
|
4907
|
+
function bcRefreshPreview() {
|
|
4908
|
+
const now = Date.now();
|
|
4909
|
+
if (now - bcPrevAt < 1500) return; /* done + idle çift tetiklemesin */
|
|
4910
|
+
bcPrevAt = now;
|
|
4911
|
+
if (!ideModeOn() || !document.body.classList.contains('browser-open')) return;
|
|
4912
|
+
beast.idePreview().catch(() => {});
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4915
|
+
/* ---------- Beast Code araç kutuları ----------
|
|
4916
|
+
Ard arda gelen araç çağrıları TEK kutuda toplanır. Kural: engine mesajı
|
|
4917
|
+
tool_calls İÇERİYORSA (iş turu) grup AÇIK kalır — aradaki ara yazılar kutu
|
|
4918
|
+
içine not olarak akar; tool_calls İÇERMEYEN saf metin (normal mesaj) gelirse
|
|
4919
|
+
grup kapanır, sonraki çağrılar YENİ kutu açar.
|
|
4920
|
+
Kutu çalışırken etrafında fosforlu açık mavi outline döner; her çağrı kutu
|
|
4921
|
+
içinde bir bölüm, çıktı max 180px scroll'lu. */
|
|
4922
|
+
const bcTools = new Map(); /* callId → { box, sec } */
|
|
4923
|
+
let bcCurBox = null; /* açık araç grubu — ard arda çağrılar buraya eklenir */
|
|
4924
|
+
let bcNoteEl = null; /* grup içindeki ara yazı notu */
|
|
4925
|
+
let bcNoteRaw = '';
|
|
4926
|
+
|
|
4927
|
+
function bcNoteDetach() {
|
|
4928
|
+
bcNoteEl = null;
|
|
4929
|
+
bcNoteRaw = '';
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4932
|
+
function bcCloseToolGroup() {
|
|
4933
|
+
/* grup kapanınca (normal mesaj / iş bitti) ring durur */
|
|
4934
|
+
if (bcCurBox) bcCurBox.classList.remove('running');
|
|
4935
|
+
bcCurBox = null;
|
|
4936
|
+
bcNoteDetach();
|
|
4937
|
+
}
|
|
4938
|
+
|
|
4939
|
+
function bcToolGroupNew() {
|
|
4940
|
+
const box = document.createElement('div');
|
|
4941
|
+
box.className = 'bc-toolbox';
|
|
4942
|
+
const body = document.createElement('div');
|
|
4943
|
+
body.className = 'bc-toolbody';
|
|
4944
|
+
box.appendChild(body);
|
|
4945
|
+
els.bcOut.appendChild(box);
|
|
4946
|
+
while (els.bcOut.childElementCount > BC_MAX_LINES) els.bcOut.removeChild(els.bcOut.firstChild);
|
|
4947
|
+
return box;
|
|
4948
|
+
}
|
|
4949
|
+
|
|
4950
|
+
function bcToolBoxStart(callId, name, args) {
|
|
4951
|
+
bcFlushStream();
|
|
4952
|
+
if (!bcCurBox) bcCurBox = bcToolGroupNew();
|
|
4953
|
+
const sec = document.createElement('div');
|
|
4954
|
+
sec.className = 'bc-toolsection';
|
|
4955
|
+
const short = termShortTool(name, args);
|
|
4956
|
+
sec.innerHTML =
|
|
4957
|
+
'<div class="bc-toolhead">' +
|
|
4958
|
+
'<span class="bc-toolspin"></span>' +
|
|
4959
|
+
'<span class="bc-toolname">▸ ' + escapeHtml(String(name || 'araç')) + '</span>' +
|
|
4960
|
+
(short ? '<span class="bc-toolar" title="' + escapeHtml(short) + '">' + escapeHtml(short) + '</span>' : '') +
|
|
4961
|
+
'</div>' +
|
|
4962
|
+
'<div class="bc-toolout" hidden></div>';
|
|
4963
|
+
/* ring: iş bitene / grup kapanana kadar bu kutuda dönmeye devam eder */
|
|
4964
|
+
bcCurBox.classList.add('running');
|
|
4965
|
+
const body = bcCurBox.querySelector('.bc-toolbody') || bcCurBox;
|
|
4966
|
+
body.appendChild(sec);
|
|
4967
|
+
if (callId) bcTools.set(callId, { box: bcCurBox, sec });
|
|
4968
|
+
bcScroll();
|
|
4969
|
+
bcBodyScroll(body);
|
|
4970
|
+
}
|
|
4971
|
+
|
|
4972
|
+
function bcToolBoxEnd(callId, ok, result) {
|
|
4973
|
+
bcFlushStream();
|
|
4974
|
+
const t = (callId && bcTools.get(callId)) || null;
|
|
4975
|
+
if (callId) bcTools.delete(callId);
|
|
4976
|
+
const out = String(result || '').replace(/\s+$/, '');
|
|
4977
|
+
if (t) {
|
|
4978
|
+
const { sec } = t;
|
|
4979
|
+
/* ring burada SÖNDÜRÜLMEZ — iş bitene kadar (bcCloseToolGroup) döner */
|
|
4980
|
+
sec.classList.add('done');
|
|
4981
|
+
if (!ok) sec.classList.add('failed');
|
|
4982
|
+
const pre = sec.querySelector('.bc-toolout');
|
|
4983
|
+
if (out) {
|
|
4984
|
+
pre.hidden = false;
|
|
4985
|
+
pre.textContent = out.length > 4000 ? out.slice(0, 4000) + '\n… (kesildi)' : out;
|
|
4986
|
+
} else {
|
|
4987
|
+
pre.remove();
|
|
4988
|
+
}
|
|
4989
|
+
bcBodyScroll(sec);
|
|
4990
|
+
} else if (out || !ok) {
|
|
4991
|
+
/* başlangıcı kaçmış çağrı — düz satır olarak düş */
|
|
4992
|
+
bcLine(ok ? 't-out' : 't-err', out ? (out.length > 400 ? out.slice(0, 400) + ' …(kesildi)' : out) : '(hata)');
|
|
4993
|
+
}
|
|
4994
|
+
bcScroll();
|
|
4995
|
+
}
|
|
4996
|
+
|
|
4997
|
+
/* ---------- Beast Code todo kartı ----------
|
|
4998
|
+
Inputun (chat) hemen üstünde ortalı kompakt kutu; #bcTodoWrap içinde yaşar,
|
|
4999
|
+
scroll'lu çıktı akışını kirletmez. */
|
|
5000
|
+
function bcRenderTodos(todos) {
|
|
5001
|
+
const list = Array.isArray(todos) ? todos : [];
|
|
5002
|
+
if (!els.bcTodoWrap) return;
|
|
5003
|
+
bcFlushStream();
|
|
5004
|
+
if (!list.length) {
|
|
5005
|
+
bcHideTodos();
|
|
5006
|
+
return;
|
|
5007
|
+
}
|
|
5008
|
+
els.bcTodoWrap.hidden = false;
|
|
5009
|
+
els.bcTodoWrap.innerHTML =
|
|
5010
|
+
'<div class="bc-todobox">' +
|
|
5011
|
+
'<div class="bc-todotitle"><span>GÖREVLER</span><span class="bc-todocount"></span></div>' +
|
|
5012
|
+
'<div class="bc-todoitems"></div>';
|
|
5013
|
+
const done = list.filter((t) => t.status === 'done').length;
|
|
5014
|
+
els.bcTodoWrap.querySelector('.bc-todocount').textContent = done + '/' + list.length;
|
|
5015
|
+
const wrap = els.bcTodoWrap.querySelector('.bc-todoitems');
|
|
5016
|
+
wrap.innerHTML = '';
|
|
5017
|
+
for (const t of list) {
|
|
5018
|
+
const st = TODO_GLYPH[t.status] ? t.status : 'pending';
|
|
5019
|
+
const row = document.createElement('div');
|
|
5020
|
+
row.className = 'bc-todoitem ' + st;
|
|
5021
|
+
row.innerHTML =
|
|
5022
|
+
'<span class="bc-todocheck">' + TODO_GLYPH[st] + '</span>' +
|
|
5023
|
+
'<span class="bc-todotext"></span>';
|
|
5024
|
+
row.querySelector('.bc-todotext').textContent = String(t.title || '');
|
|
5025
|
+
wrap.appendChild(row);
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
|
|
5029
|
+
function bcHideTodos() {
|
|
5030
|
+
if (els.bcTodoWrap) {
|
|
5031
|
+
els.bcTodoWrap.hidden = true;
|
|
5032
|
+
els.bcTodoWrap.innerHTML = '';
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
|
|
5036
|
+
/* Ara yazı → açık grup varsa kutu İÇİNE not olarak akar (kronoloji korunur) */
|
|
5037
|
+
function bcNoteStream(delta) {
|
|
5038
|
+
if (!bcCurBox) return false;
|
|
5039
|
+
if (!bcNoteEl) {
|
|
5040
|
+
bcNoteEl = document.createElement('div');
|
|
5041
|
+
bcNoteEl.className = 'bc-toolnote';
|
|
5042
|
+
const body = bcCurBox.querySelector('.bc-toolbody') || bcCurBox;
|
|
5043
|
+
body.appendChild(bcNoteEl);
|
|
5044
|
+
}
|
|
5045
|
+
bcNoteRaw += String(delta || '');
|
|
5046
|
+
bcNoteEl.textContent = bcNoteRaw.slice(-2000);
|
|
5047
|
+
bcScroll(true);
|
|
5048
|
+
bcBodyScroll(bcNoteEl);
|
|
5049
|
+
return true;
|
|
5050
|
+
}
|
|
5051
|
+
|
|
5052
|
+
/* Beast Code oturumunun engine olayları → panel çıktısı */
|
|
5053
|
+
function bcIngest(ev) {
|
|
5054
|
+
switch (ev.type) {
|
|
5055
|
+
case 'token':
|
|
5056
|
+
/* grup açıksa ara not (kutu içi); değilse normal akış */
|
|
5057
|
+
if (bcNoteStream(ev.delta)) break;
|
|
5058
|
+
bcStreamDelta(ev.delta);
|
|
5059
|
+
break;
|
|
5060
|
+
case 'message':
|
|
5061
|
+
if (ev.message && ev.message.role === 'assistant') {
|
|
5062
|
+
const content = typeof ev.message.content === 'string' ? ev.message.content : '';
|
|
5063
|
+
const hasTools = Array.isArray(ev.message.tool_calls) && ev.message.tool_calls.length > 0;
|
|
5064
|
+
if (hasTools) {
|
|
5065
|
+
/* iş turu: grup AÇIK kalır — ard arda araçlar aynı kutuda sürer.
|
|
5066
|
+
Kutu içine akmış ara not kalıcı olur, işaretçileri bırak */
|
|
5067
|
+
bcNoteDetach();
|
|
5068
|
+
bcFlushStream();
|
|
5069
|
+
break;
|
|
5070
|
+
}
|
|
5071
|
+
/* NORMAL MESAJ: kutu içine düşen taslak notu çıkar, cevabı düz metin bas */
|
|
5072
|
+
if (bcNoteEl) { bcNoteEl.remove(); bcNoteDetach(); }
|
|
5073
|
+
if (content && !bcStreamRaw.trim()) bcLine('t-out', content);
|
|
5074
|
+
bcCloseToolGroup();
|
|
5075
|
+
bcFlushStream();
|
|
5076
|
+
}
|
|
5077
|
+
break;
|
|
5078
|
+
case 'todos':
|
|
5079
|
+
bcRenderTodos(ev.todos);
|
|
5080
|
+
break;
|
|
5081
|
+
case 'tool-start':
|
|
5082
|
+
bcToolBoxStart(ev.callId, ev.name, ev.args);
|
|
5083
|
+
break;
|
|
5084
|
+
case 'tool-end':
|
|
5085
|
+
bcToolBoxEnd(ev.callId, ev.ok, ev.result);
|
|
5086
|
+
break;
|
|
5087
|
+
case 'done':
|
|
5088
|
+
bcFlushStream();
|
|
5089
|
+
bcCloseToolGroup();
|
|
5090
|
+
bcSetBusy(false);
|
|
5091
|
+
bcLine('t-dim', '(tamamlandı)');
|
|
5092
|
+
bcRefreshPreview();
|
|
5093
|
+
if (ideModeOn() && els.bcInput) els.bcInput.focus();
|
|
5094
|
+
break;
|
|
5095
|
+
case 'error':
|
|
5096
|
+
bcFlushStream();
|
|
5097
|
+
bcCloseToolGroup();
|
|
5098
|
+
bcSetBusy(false);
|
|
5099
|
+
bcLine('t-err', '[hata] ' + (ev.error || ''));
|
|
5100
|
+
break;
|
|
5101
|
+
case 'status':
|
|
5102
|
+
/* 'idle' = engine oturumu GERÇEKTEN bıraktı (finally bloğu, her yolda gelir) —
|
|
5103
|
+
done kaçsa bile panel kilitli kalmaz, ■ otomatik kalkar, ring durur */
|
|
5104
|
+
if (ev.status === 'idle') {
|
|
5105
|
+
bcCloseToolGroup();
|
|
5106
|
+
bcSetBusy(false);
|
|
5107
|
+
bcRefreshPreview();
|
|
5108
|
+
}
|
|
5109
|
+
break;
|
|
5110
|
+
}
|
|
5111
|
+
}
|
|
5112
|
+
|
|
5113
|
+
function bcRunCurrent() {
|
|
5114
|
+
const msg = els.bcInput.value.trim();
|
|
5115
|
+
if (!msg) return;
|
|
5116
|
+
els.bcInput.value = '';
|
|
5117
|
+
bcLine('t-cmd', 'code> ' + msg);
|
|
5118
|
+
bcHideTodos(); /* yeni sorgu = eski todo list temizlenir; agent yeniden basacak */
|
|
5119
|
+
bcSetBusy(true);
|
|
5120
|
+
/* sürüyor/serbest kararını main'deki gerçek engine.isBusy verir —
|
|
5121
|
+
panel bayat kilitli kaldıysa ilk mesajla kendini düzeltir */
|
|
5122
|
+
beast.beastcodeSend(msg).then((r) => {
|
|
5123
|
+
if (r && r.ok) {
|
|
5124
|
+
bcSessionId = r.sessionId;
|
|
5125
|
+
} else if (r && r.busy) {
|
|
5126
|
+
/* iş gerçekten sürüyor — kilit açılmaz, bilgi satırı düşer */
|
|
5127
|
+
bcLine('t-dim', (r && r.error) || 'mesaj sürüyor');
|
|
5128
|
+
} else {
|
|
5129
|
+
bcSetBusy(false);
|
|
5130
|
+
bcLine('t-err', (r && r.error) || 'gönderilemedi');
|
|
5131
|
+
}
|
|
5132
|
+
}).catch((e) => {
|
|
5133
|
+
bcSetBusy(false);
|
|
5134
|
+
bcLine('t-err', String((e && e.message) || e));
|
|
5135
|
+
});
|
|
5136
|
+
}
|
|
5137
|
+
|
|
5138
|
+
if (els.bcInput) els.bcInput.addEventListener('keydown', (e) => {
|
|
5139
|
+
if (e.key === 'Enter') { e.preventDefault(); bcRunCurrent(); }
|
|
5140
|
+
});
|
|
5141
|
+
if (els.bcStop) els.bcStop.addEventListener('click', () => beast.beastcodeStop().catch(() => {}));
|
|
5142
|
+
if (els.bcClear) els.bcClear.addEventListener('click', () => {
|
|
5143
|
+
els.bcOut.innerHTML = '';
|
|
5144
|
+
bcTools.clear();
|
|
5145
|
+
bcCloseToolGroup();
|
|
5146
|
+
bcHideTodos();
|
|
5147
|
+
bcFlushStream();
|
|
5148
|
+
});
|
|
5149
|
+
if (els.bcNew) els.bcNew.addEventListener('click', async () => {
|
|
5150
|
+
if (bcRunning) { toast('Mesaj sürüyor — önce ■ ile durdur'); return; }
|
|
5151
|
+
const r = await beast.beastcodeNew().catch(() => null);
|
|
5152
|
+
if (r && r.ok) {
|
|
5153
|
+
bcSessionId = null;
|
|
5154
|
+
bcTools.clear();
|
|
5155
|
+
bcCloseToolGroup();
|
|
5156
|
+
bcHideTodos();
|
|
5157
|
+
bcFlushStream();
|
|
5158
|
+
bcLine('t-sys', 'yeni oturum — sonraki mesaj taze başlar');
|
|
5159
|
+
}
|
|
5160
|
+
});
|
|
5161
|
+
/* Uygulama her açılışta agent (chat) modunda başlar — IDE modu yalnız
|
|
5162
|
+
kullanıcı butona basınca girilir (oturumluk). */
|