beast-agent 1.8.0 → 2.0.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.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +130 -128
  3. package/bin/beast-agent.js +137 -137
  4. package/package.json +1 -1
  5. package/scripts/fix-electron.js +138 -138
  6. package/scripts/release.js +137 -131
  7. package/scripts/swap-electron.js +40 -40
  8. package/src/agent/agentdefs.js +122 -122
  9. package/src/agent/bots.js +580 -580
  10. package/src/agent/bus.js +389 -389
  11. package/src/agent/computeruse.js +200 -200
  12. package/src/agent/config.js +284 -284
  13. package/src/agent/discord.js +332 -332
  14. package/src/agent/engine.js +85 -11
  15. package/src/agent/kb.js +123 -123
  16. package/src/agent/llm.js +430 -430
  17. package/src/agent/logger.js +90 -90
  18. package/src/agent/mcp.js +427 -0
  19. package/src/agent/mem0.js +605 -605
  20. package/src/agent/memory.js +427 -427
  21. package/src/agent/mqueue.js +124 -124
  22. package/src/agent/pdf.js +20 -20
  23. package/src/agent/research.js +133 -133
  24. package/src/agent/scripts/news.py +113 -113
  25. package/src/agent/scripts/stealthsearch.py +30 -30
  26. package/src/agent/scripts/websearch.py +225 -225
  27. package/src/agent/searxng.js +325 -325
  28. package/src/agent/seeds/brainstorming/SKILL.md +90 -90
  29. package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
  30. package/src/agent/seeds/executing-plans/SKILL.md +60 -60
  31. package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
  32. package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
  33. package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
  34. package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
  35. package/src/agent/seeds/writing-plans/SKILL.md +162 -162
  36. package/src/agent/seeds/writing-skills/SKILL.md +229 -229
  37. package/src/agent/skills.js +652 -651
  38. package/src/agent/store.js +378 -378
  39. package/src/agent/telegram.js +155 -155
  40. package/src/agent/tokens.js +39 -39
  41. package/src/agent/usage.js +125 -125
  42. package/src/agent/watchers.js +312 -312
  43. package/src/agent/watext.js +80 -80
  44. package/src/agent/whatsapp.js +555 -555
  45. package/src/cron.js +255 -255
  46. package/src/main.js +385 -6
  47. package/src/preload.js +13 -0
  48. package/src/renderer/browserPreload.js +73 -73
  49. package/src/renderer/i18n.js +38 -0
  50. package/src/renderer/index.html +448 -407
  51. package/src/renderer/renderer.js +897 -42
  52. package/src/renderer/style.css +275 -11
@@ -80,6 +80,28 @@ const els = {
80
80
  bcAttach: $('#bcAttach'),
81
81
  bcMic: $('#bcMic'),
82
82
  bcChips: $('#bcChips'),
83
+ studioBtn: $('#studioBtn'),
84
+ stPanel: $('#stPanel'),
85
+ stTitle: $('#stTitle'),
86
+ stCwd: $('#stCwd'),
87
+ stOut: $('#stOut'),
88
+ stTodoWrap: $('#stTodoWrap'),
89
+ stStatus: $('#stStatus'),
90
+ stChips: $('#stChips'),
91
+ stInput: $('#stInput'),
92
+ stStop: $('#stStop'),
93
+ stClear: $('#stClear'),
94
+ stNew: $('#stNew'),
95
+ stAttach: $('#stAttach'),
96
+ stMic: $('#stMic'),
97
+ studioRow: $('#studioRow'),
98
+ stSplit: $('#stSplit'),
99
+ stVideoEl: $('#stVideoEl'),
100
+ stVideoName: $('#stVideoName'),
101
+ stVideoOpen: $('#stVideoOpen'),
102
+ stVideoExt: $('#stVideoExt'),
103
+ stVideoBody: $('#stVideoBody'),
104
+ stVideoEmpty: $('#stVideoEmpty'),
83
105
  codeTabs: $('#codeTabs'),
84
106
  codeTa: $('#codeTa'),
85
107
  codeGutter: $('#codeGutter'),
@@ -881,8 +903,9 @@ async function openSession(id) {
881
903
 
882
904
  const TEXT_EXT = /\.(txt|md|json|csv|log|js|ts|py|ps1|bat|cmd|html|css|yaml|yml|xml|ini)$/i;
883
905
 
884
- /* ek hedefleri: chat + Beast Code panosu ayrı kuyruklar */
906
+ /* ek hedefleri: chat + Beast Code + Beast Studio ayrı kuyruklar */
885
907
  let bcPending = [];
908
+ let stPending = [];
886
909
 
887
910
  function renderChips() {
888
911
  els.chips.innerHTML = '';
@@ -925,25 +948,49 @@ function renderBcChips() {
925
948
  });
926
949
  }
927
950
 
951
+ function renderStChips() {
952
+ if (!els.stChips) return;
953
+ els.stChips.innerHTML = '';
954
+ els.stChips.hidden = stPending.length === 0;
955
+ stPending.forEach((a, i) => {
956
+ const c = document.createElement('span');
957
+ c.className = 'chip';
958
+ const label = a.type === 'image' ? `\u25A3 ${a.name}` : `\u2261 ${a.name}`;
959
+ c.appendChild(document.createTextNode(label));
960
+ const x = document.createElement('span');
961
+ x.className = 'x';
962
+ x.textContent = '×';
963
+ x.addEventListener('click', () => {
964
+ stPending.splice(i, 1);
965
+ renderStChips();
966
+ });
967
+ c.appendChild(x);
968
+ els.stChips.appendChild(c);
969
+ });
970
+ }
971
+
928
972
  function addFiles(files, mode) {
929
973
  const isBc = mode === 'bc';
974
+ const isSt = mode === 'st';
975
+ const getList = () => (isBc ? bcPending : isSt ? stPending : pending);
976
+ const paint = () => (isBc ? renderBcChips() : isSt ? renderStChips() : renderChips());
930
977
  for (const f of files) {
931
978
  if (f.size > 8 * 1024 * 1024) { toast('Çok büyük (max 8MB): ' + f.name); continue; }
932
979
  if (f.type.startsWith('image/')) {
933
980
  const r = new FileReader();
934
981
  r.onload = () => {
935
- const list = isBc ? bcPending : pending;
982
+ const list = getList();
936
983
  if (list.length >= 6) { toast('En fazla 6 ek'); return; }
937
984
  list.push({ type: 'image', name: f.name, dataUrl: String(r.result) });
938
- isBc ? renderBcChips() : renderChips();
985
+ paint();
939
986
  };
940
987
  r.readAsDataURL(f);
941
988
  } else if (TEXT_EXT.test(f.name)) {
942
989
  f.text().then((t) => {
943
- const list = isBc ? bcPending : pending;
990
+ const list = getList();
944
991
  if (list.length >= 6) { toast('En fazla 6 ek'); return; }
945
992
  list.push({ type: 'file', name: f.name, content: t.slice(0, 200000) });
946
- isBc ? renderBcChips() : renderChips();
993
+ paint();
947
994
  });
948
995
  } else {
949
996
  toast('Desteklenmeyen tür: ' + f.name);
@@ -1013,6 +1060,7 @@ async function renderActiveSettingsTab() {
1013
1060
  case 'email': await renderEmailPane(); break;
1014
1061
  case 'integrations': await renderIntegrationsPane(); break;
1015
1062
  case 'websearch': await renderWebSearchPane(); break;
1063
+ case 'mcp': await renderMcpPane(); break;
1016
1064
  case 'events': await renderEventsPane(); break;
1017
1065
  case 'cron': await openCron(); break;
1018
1066
  case 'usage': await renderUsagePane(); break;
@@ -1024,12 +1072,82 @@ async function renderActiveSettingsTab() {
1024
1072
  }
1025
1073
  }
1026
1074
 
1075
+ async function renderMcpPane() {
1076
+ const pane = $('#tab-mcp');
1077
+ if (!pane) return;
1078
+ const st = await beast.mcpStatus().catch(() => ({ path: '', servers: [] }));
1079
+ const cfg = await beast.mcpConfigGet().catch(() => ({ path: '', raw: '' }));
1080
+ const stateT = (s) => _t('mcp_state_' + s) || String(s || '').toUpperCase();
1081
+ let rows = '';
1082
+ if (!(st.servers || []).length) {
1083
+ rows = '<div class="sub">' + _t('mcp_none') + '</div>';
1084
+ }
1085
+ for (const s of st.servers || []) {
1086
+ rows +=
1087
+ '<div style="display:flex;align-items:center;gap:8px;padding:8px;border:1px solid var(--border);border-radius:8px;margin-bottom:6px;flex-wrap:wrap">' +
1088
+ '<span style="font-weight:600">' + escapeHtml(s.name) + '</span>' +
1089
+ '<span class="sub" style="margin:0;max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escapeHtml(s.command + ' ' + (s.args || []).join(' ')) + '</span>' +
1090
+ '<span style="color:var(--muted);font-size:12px">' + stateT(s.state) + (s.enabled && s.toolCount ? ' · ' + s.toolCount + ' ' + _t('mcp_tool_count') : '') + '</span>' +
1091
+ (s.lastError && s.state === 'down' ? '<span class="sub" style="color:#e06c75;margin:0;max-width:30%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escapeHtml(s.lastError.slice(0, 90)) + '</span>' : '') +
1092
+ '<span style="flex:1"></span>' +
1093
+ '<button class="btn mcp-tgl" data-n="' + escapeHtml(s.name) + '" style="width:auto;padding:2px 10px">' + (s.enabled ? '⏸' : '▶') + '</button>' +
1094
+ '<button class="btn ghost mcp-ref" data-n="' + escapeHtml(s.name) + '" style="width:auto;padding:2px 10px">' + _t('mcp_refresh') + '</button>' +
1095
+ '</div>';
1096
+ }
1097
+ pane.innerHTML =
1098
+ '<h2>' + _t('mcp_h2') + '</h2>' +
1099
+ '<div class="sub">' + _t('mcp_sub') + '</div>' +
1100
+ '<div id="mcpList" style="margin-top:10px">' + rows + '</div>' +
1101
+ '<div class="divider"></div>' +
1102
+ '<h2>' + _t('mcp_json_h2') + '</h2>' +
1103
+ '<div class="sub">' + _t('mcp_json_sub') + '</div>' +
1104
+ '<textarea id="mcpJson" class="inp" rows="10" spellcheck="false" style="margin-top:8px;font-family:monospace;font-size:12px"></textarea>' +
1105
+ '<div style="display:flex;gap:8px;margin-top:8px">' +
1106
+ '<button id="mcpSave" class="btn">' + _t('mcp_save') + '</button>' +
1107
+ '<span id="mcpMsg" class="sub" style="margin:0;align-self:center"></span></div>';
1108
+ const box = $('#mcpJson');
1109
+ box.value = cfg.raw || JSON.stringify({ servers: {} }, null, 2);
1110
+ $('#mcpSave').addEventListener('click', async () => {
1111
+ const msg = $('#mcpMsg');
1112
+ const r = await beast.mcpConfigSet(box.value).catch(() => ({ ok: false, error: 'IPC' }));
1113
+ if (r && r.ok) {
1114
+ msg.textContent = _t('mcp_saved');
1115
+ msg.style.color = '';
1116
+ renderMcpPane();
1117
+ } else {
1118
+ msg.textContent = _t('mcp_save_err') + ((r && r.error) || '?');
1119
+ msg.style.color = '#e06c75';
1120
+ }
1121
+ });
1122
+ pane.querySelectorAll('.mcp-ref').forEach((b) =>
1123
+ b.addEventListener('click', async () => {
1124
+ b.disabled = true;
1125
+ await beast.mcpRefresh(b.dataset.n).catch(() => {});
1126
+ renderMcpPane();
1127
+ })
1128
+ );
1129
+ pane.querySelectorAll('.mcp-tgl').forEach((b) =>
1130
+ b.addEventListener('click', async () => {
1131
+ b.disabled = true;
1132
+ try {
1133
+ const obj = JSON.parse(box.value || '{}');
1134
+ const name = b.dataset.n;
1135
+ if (obj.servers && obj.servers[name]) {
1136
+ obj.servers[name].enabled = obj.servers[name].enabled === false;
1137
+ await beast.mcpConfigSet(JSON.stringify(obj));
1138
+ }
1139
+ } catch {}
1140
+ renderMcpPane();
1141
+ })
1142
+ );
1143
+ }
1144
+
1027
1145
  function switchTab(name) {
1028
1146
  setTab = name;
1029
1147
  document.querySelectorAll('#setTabs .tab').forEach((b) =>
1030
1148
  b.classList.toggle('active', b.dataset.tab === name)
1031
1149
  );
1032
- for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'email', 'integrations', 'websearch', 'events', 'cron', 'usage', 'logs', 'dash', 'sec', 'update']) {
1150
+ for (const p of ['lang', 'provider', 'fallout', 'skills', 'agents', 'tts', 'email', 'integrations', 'websearch', 'mcp', 'events', 'cron', 'usage', 'logs', 'dash', 'sec', 'update']) {
1033
1151
  const el = $('#tab-' + p);
1034
1152
  if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
1035
1153
  }
@@ -1043,6 +1161,7 @@ function switchTab(name) {
1043
1161
  if (name === 'update') renderUpdatePane(true);
1044
1162
  if (name === 'agents') refreshAgentsPane();
1045
1163
  if (name === 'websearch') renderWebSearchPane();
1164
+ if (name === 'mcp') renderMcpPane();
1046
1165
  /* Fallout: her açılışta güncel provider zincirini çek */
1047
1166
  if (name === 'fallout') refreshFalloutPane();
1048
1167
  }
@@ -4173,6 +4292,12 @@ function renderAgentsPane() {
4173
4292
  function onEvent(ev) {
4174
4293
  if (ev.type === 'sessions') { refreshSessions(); return; }
4175
4294
  if (ev.type === 'approval') {
4295
+ /* Beast Studio oturumunun onayı: panelde ipucu + kart sohbete düşer */
4296
+ if (stSessionId && ev.sessionId === stSessionId) {
4297
+ showApprovalCard(ev);
4298
+ stLine('t-dim', '⏳ onay bekleniyor: ' + (ev.tool || '?') + ' — chat moduna geçip kartı onayla');
4299
+ return;
4300
+ }
4176
4301
  /* Beast Code oturumunun onayı: panelde ipucu + kart sohbete düşer */
4177
4302
  if (bcSessionId && ev.sessionId === bcSessionId) {
4178
4303
  showApprovalCard(ev);
@@ -4198,6 +4323,12 @@ function onEvent(ev) {
4198
4323
  }
4199
4324
  /* OFFLINE MESAJ KUYRUĞU: bağlantı + kuyruk olayları (sessionId filtresinden önce) */
4200
4325
  if (ev.type === 'net' || ev.type === 'netQueue') { onNetEvent(ev); return; }
4326
+ /* Beast Studio oturumu: olaylar SADECE Studio paneline akar — ana sohbeti
4327
+ ve Beast Code panelini kirletmez (dünyalar ayrı) */
4328
+ if (stSessionId && ev.sessionId === stSessionId) {
4329
+ stIngest(ev);
4330
+ return;
4331
+ }
4201
4332
  /* Beast Code oturumu (IDE modu ortasındaki panel): olayları panele akıt,
4202
4333
  ana sohbeti kirletme */
4203
4334
  if (bcSessionId && ev.sessionId === bcSessionId) {
@@ -5137,12 +5268,15 @@ async function init() {
5137
5268
  document.addEventListener('langchange', syncSttLang);
5138
5269
  }
5139
5270
 
5140
- /* + butonu iki hedefe hizmet eder: chat composer VE Beast Code paneli */
5271
+ /* + butonu üç hedefe hizmet eder: chat composer, Beast Code VE Beast Studio */
5141
5272
  let fileSink = 'chat';
5142
5273
  els.attachBtn.addEventListener('click', () => { fileSink = 'chat'; els.fileInput.click(); });
5143
5274
  if (els.bcAttach) {
5144
5275
  els.bcAttach.addEventListener('click', () => { fileSink = 'bc'; els.fileInput.click(); });
5145
5276
  }
5277
+ if (els.stAttach) {
5278
+ els.stAttach.addEventListener('click', () => { fileSink = 'st'; els.fileInput.click(); });
5279
+ }
5146
5280
  els.fileInput.addEventListener('change', () => {
5147
5281
  addFiles([...els.fileInput.files], fileSink);
5148
5282
  els.fileInput.value = '';
@@ -5204,6 +5338,12 @@ async function init() {
5204
5338
  els.bcInput.focus();
5205
5339
  bcInputResize();
5206
5340
  });
5341
+ setupMicButton(els.stMic, (t) => {
5342
+ if (!els.stInput) return;
5343
+ els.stInput.value = (els.stInput.value ? els.stInput.value.replace(/\s+$/, '') + ' ' : '') + t;
5344
+ els.stInput.focus();
5345
+ stInputResize();
5346
+ });
5207
5347
 
5208
5348
  beast.onWaEvent(onWaEvent);
5209
5349
  beast.onTgEvent(onTgEvent);
@@ -6072,13 +6212,27 @@ $('#ghAllRefresh').addEventListener('click', () => {
6072
6212
  Dosyaya tıkla → editör; klasöre tıkla → aç/kapa. */
6073
6213
 
6074
6214
  const IDE_ICONS = {
6075
- '.html': '🌐', '.htm': '🌐', '.css': '🎨', '.js': '📜', '.mjs': '📜', '.cjs': '📜',
6076
- '.ts': '📜', '.jsx': '📜', '.tsx': '📜', '.json': '🧾', '.md': '📝', '.txt': '📄',
6077
- '.py': '🐍', '.ps1': '⚙️', '.bat': '⚙️', '.cmd': '⚙️', '.sh': '⚙️', '.yaml': '🧾',
6078
- '.yml': '🧾', '.png': '🖼️', '.jpg': '🖼️', '.jpeg': '🖼️', '.gif': '🖼️', '.svg': '🖼️',
6079
- '.pdf': '📕',
6215
+ '.html': '🌐', '.htm': '🌐', '.css': '🎨', '.scss': '🎨', '.sass': '🎨', '.less': '🎨',
6216
+ '.js': '📜', '.mjs': '📜', '.cjs': '📜', '.ts': '📜', '.mts': '📜', '.cts': '📜',
6217
+ '.jsx': '📜', '.tsx': '📜', '.vue': '📜', '.svelte': '📜', '.astro': '📜',
6218
+ '.json': '🧾', '.jsonc': '🧾', '.yaml': '🧾', '.yml': '🧾', '.toml': '🧾', '.xml': '🧾',
6219
+ '.md': '📝', '.mdx': '📝', '.txt': '📄', '.log': '📄', '.csv': '📊', '.tsv': '📊',
6220
+ '.py': '🐍', '.go': '🐹', '.rs': '🦀', '.java': '☕', '.kt': '🧩', '.swift': '🕊️',
6221
+ '.rb': '💎', '.php': '🐘', '.cs': '🎯', '.c': '🔧', '.h': '🔧', '.cpp': '🔧',
6222
+ '.hpp': '🔧', '.sql': '🗄️', '.sh': '⚙️', '.ps1': '⚙️', '.bat': '⚙️', '.cmd': '⚙️',
6223
+ '.lua': '🌙', '.r': '📈', '.dart': '🎯', '.graphql': '◈', '.proto': '◈',
6224
+ '.png': '🖼️', '.jpg': '🖼️', '.jpeg': '🖼️', '.gif': '🖼️', '.svg': '🖼️', '.webp': '🖼️',
6225
+ '.pdf': '📕', '.env': '🔑',
6226
+ /* Studio medya ikonları — IDE ağacında da doğru simge */
6227
+ '.mp4': '🎬', '.mkv': '🎬', '.mov': '🎬', '.avi': '🎬', '.webm': '🎬', '.m4v': '🎬',
6228
+ '.mpg': '🎬', '.mpeg': '🎬', '.wmv': '🎬', '.flv': '🎬', '.3gp': '🎬', '.ogv': '🎬',
6229
+ '.mp3': '🎵', '.wav': '🎵', '.ogg': '🎵', '.m4a': '🎵', '.flac': '🎵', '.aac': '🎵', '.opus': '🎵',
6230
+ '.srt': '💬', '.vtt': '💬', '.ass': '💬', '.ssa': '💬',
6080
6231
  };
6081
6232
 
6233
+ /* Studio modunda ağaçta TIKLANAN medya dosyaları video bölümünde oynatılır */
6234
+ const MEDIA_FILE_RE = /\.(mp4|mkv|mov|avi|webm|m4v|mpg|mpeg|wmv|flv|3gp|ogv|mp3|wav|ogg|m4a|flac|aac|opus)$/i;
6235
+
6082
6236
  const ideState = {
6083
6237
  cache: new Map(), // rel -> entries[]
6084
6238
  open: new Set(['']),
@@ -6090,8 +6244,10 @@ function ideModeOn() {
6090
6244
  }
6091
6245
 
6092
6246
  async function setIdeMode(on) {
6247
+ if (on && studioModeOn()) await setStudioMode(false); /* Studio açıkken IDE'ye geçiş — Studio kapanır */
6093
6248
  document.body.classList.toggle('ide-mode', !!on);
6094
- /* soldaki marka: chat modunda BEAST Agent, IDE modunda BEAST Code */
6249
+ if (els.ideBtn) els.ideBtn.classList.toggle('on', !!on);
6250
+ /* soldaki marka: chat modunda BEAST Agent, IDE modunda BEAST Code, Studio modunda BEAST Studio */
6095
6251
  const brandSub = document.querySelector('#brand .brand-sub');
6096
6252
  if (brandSub) brandSub.textContent = on ? 'Code' : 'Agent';
6097
6253
  /* rail her iki modda da DEFAULT KAPALI — çalışan paralel ajan varsa
@@ -6116,6 +6272,166 @@ async function setIdeMode(on) {
6116
6272
  }
6117
6273
  }
6118
6274
 
6275
+ /* ================= BEAST STUDIO MODU =================
6276
+ Beast Code ile AYRI dünya: sol klasör konsolu (başlık: BEAST STUDIO),
6277
+ sağ → chat + video bölümü. Oturumlar main tarafında studioRoot bazlı ve
6278
+ ÖZELDİR — chat geçmişi listesine VE Beast Code paneline karışmaz. */
6279
+
6280
+ const studioState = {
6281
+ cache: new Map(),
6282
+ open: new Set(['']),
6283
+ path: '',
6284
+ };
6285
+
6286
+ function studioModeOn() {
6287
+ return document.body.classList.contains('studio-mode');
6288
+ }
6289
+
6290
+ async function setStudioMode(on) {
6291
+ if (on && ideModeOn()) await setIdeMode(false); /* IDE açıkken Studio'ya geçiş — IDE kapanır */
6292
+ document.body.classList.toggle('studio-mode', !!on);
6293
+ if (els.studioBtn) els.studioBtn.classList.toggle('on', !!on);
6294
+ const brandSub = document.querySelector('#brand .brand-sub');
6295
+ if (brandSub) brandSub.textContent = on ? 'Studio' : 'Agent';
6296
+ /* klasör konsolunun en üstü: Studio modunda "BEAST STUDIO" yazar */
6297
+ const ftp = document.getElementById('filePanelTitle');
6298
+ if (ftp) {
6299
+ if (on) {
6300
+ ftp.textContent = 'BEAST STUDIO';
6301
+ ftp.removeAttribute('data-i18n');
6302
+ } else {
6303
+ ftp.setAttribute('data-i18n', 'ide_files');
6304
+ ftp.textContent = (window.I18N && window.I18N.t('ide_files')) || 'DOSYALAR';
6305
+ }
6306
+ }
6307
+ toggleRail(true);
6308
+ railManualOpen = false;
6309
+ /* Studio'da web önizleme yok — açık tarayıcıyı her iki yönde de kapat */
6310
+ if (document.body.classList.contains('browser-open')) {
6311
+ try { beast.toggleBrowser(); } catch {}
6312
+ }
6313
+ if (on) {
6314
+ stSplitApply();
6315
+ await loadStudioTree();
6316
+ stBanner();
6317
+ } else {
6318
+ stVideoStop();
6319
+ }
6320
+ }
6321
+
6322
+ async function loadStudioTree() {
6323
+ studioState.cache.clear();
6324
+ await renderFileTree();
6325
+ }
6326
+
6327
+ /* ---------- chat/video ayırıcı (IDE split ile aynı model) ---------- */
6328
+ let stSplitFrac = 0;
6329
+
6330
+ function stSplitApply() {
6331
+ if (!stSplitFrac) {
6332
+ let saved = 0;
6333
+ try { saved = parseFloat(localStorage.getItem('beast.studioSplit')) || 0; } catch {}
6334
+ stSplitFrac = saved > 0.05 && saved < 0.95 ? saved : 0.48;
6335
+ }
6336
+ stSplitFrac = Math.max(0.2, Math.min(stSplitFrac, 0.8));
6337
+ const rowW = els.studioRow && els.studioRow.clientWidth ? els.studioRow.clientWidth : window.innerWidth - 250;
6338
+ const w = Math.max(300, Math.min(Math.round(rowW * stSplitFrac), Math.max(340, rowW - 260)));
6339
+ document.body.style.setProperty('--stSplit', w + 'px');
6340
+ }
6341
+
6342
+ if (els.stSplit) {
6343
+ els.stSplit.addEventListener('mousedown', (e) => {
6344
+ e.preventDefault();
6345
+ els.stSplit.classList.add('dragging');
6346
+ const rowLeft = els.stPanel ? els.stPanel.getBoundingClientRect().left : 250;
6347
+ const rowW = Math.max(600, els.studioRow && els.studioRow.clientWidth ? els.studioRow.clientWidth : window.innerWidth - 250);
6348
+ const move = (ev) => {
6349
+ const w = Math.max(300, Math.min(ev.clientX - rowLeft, rowW - 260));
6350
+ stSplitFrac = Math.max(0.2, Math.min(w / rowW, 0.8));
6351
+ document.body.style.setProperty('--stSplit', Math.round(w) + 'px');
6352
+ };
6353
+ const up = () => {
6354
+ document.removeEventListener('mousemove', move);
6355
+ document.removeEventListener('mouseup', up);
6356
+ els.stSplit.classList.remove('dragging');
6357
+ try { localStorage.setItem('beast.studioSplit', String(stSplitFrac)); } catch {}
6358
+ };
6359
+ document.addEventListener('mousemove', move);
6360
+ document.addEventListener('mouseup', up);
6361
+ });
6362
+ window.addEventListener('resize', () => {
6363
+ if (studioModeOn()) stSplitApply();
6364
+ });
6365
+ }
6366
+
6367
+ /* ---------- video bölümü ----------
6368
+ Sol ağaçtan video/ses seçilir veya "Aç" ile dosya seçilir; ajanın ffmpeg
6369
+ çıktıları (output/ klasörü) ağaçta belirdiği an burada oynatılabilir. */
6370
+ let stVideoPath = '';
6371
+
6372
+ function stVideoFileUrl(abs) {
6373
+ let p = String(abs || '').replace(/\\/g, '/');
6374
+ if (!p.startsWith('/')) p = '/' + p;
6375
+ return 'file://' + p.split('/').map((seg) => encodeURIComponent(seg)).join('/');
6376
+ }
6377
+
6378
+ function stVideoPlayPath(abs) {
6379
+ if (!els.stVideoEl) return;
6380
+ stVideoPath = String(abs || '');
6381
+ els.stVideoEl.src = stVideoFileUrl(stVideoPath);
6382
+ els.stVideoEl.hidden = false;
6383
+ if (els.stVideoEmpty) els.stVideoEmpty.hidden = true;
6384
+ const nm = stVideoPath.split(/[\\/]/).pop() || '—';
6385
+ if (els.stVideoName) { els.stVideoName.textContent = nm; els.stVideoName.title = stVideoPath; }
6386
+ els.stVideoEl.play().catch(() => {});
6387
+ }
6388
+
6389
+ function stVideoPlay(rel) {
6390
+ const root = studioState.path;
6391
+ if (!root || !rel) return;
6392
+ stVideoPlayPath(root + '\\' + String(rel).replace(/\//g, '\\'));
6393
+ }
6394
+
6395
+ function stVideoStop() {
6396
+ if (!els.stVideoEl) return;
6397
+ try { els.stVideoEl.pause(); } catch {}
6398
+ els.stVideoEl.removeAttribute('src');
6399
+ try { els.stVideoEl.load(); } catch {}
6400
+ els.stVideoEl.hidden = true;
6401
+ if (els.stVideoEmpty) els.stVideoEmpty.hidden = false;
6402
+ if (els.stVideoName) els.stVideoName.textContent = '—';
6403
+ stVideoPath = '';
6404
+ }
6405
+
6406
+ if (els.stVideoOpen) {
6407
+ els.stVideoOpen.addEventListener('click', async () => {
6408
+ const r = await beast.studioPickVideo().catch(() => null);
6409
+ if (r && r.ok && r.path) stVideoPlayPath(r.path);
6410
+ else if (r && !r.canceled && r.error) toast(r.error);
6411
+ });
6412
+ }
6413
+ if (els.stVideoExt) {
6414
+ els.stVideoExt.addEventListener('click', () => {
6415
+ if (!stVideoPath) { toast('Açık video yok'); return; }
6416
+ beast.openPath(stVideoPath).catch(() => {});
6417
+ });
6418
+ }
6419
+ /* sürükle-bırak: video dosyası doğrudan video bölümüne bırakılabilir */
6420
+ if (els.stVideoBody) {
6421
+ els.stVideoBody.addEventListener('dragover', (e) => {
6422
+ e.preventDefault();
6423
+ els.stVideoBody.classList.add('dragover');
6424
+ });
6425
+ els.stVideoBody.addEventListener('dragleave', () => els.stVideoBody.classList.remove('dragover'));
6426
+ els.stVideoBody.addEventListener('drop', (e) => {
6427
+ e.preventDefault();
6428
+ els.stVideoBody.classList.remove('dragover');
6429
+ const f = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];
6430
+ if (f && f.path && MEDIA_FILE_RE.test(f.name)) stVideoPlayPath(f.path);
6431
+ else if (f) toast('Medya dosyası değil: ' + f.name);
6432
+ });
6433
+ }
6434
+
6119
6435
  /* ---------- editör panelini kapat/göster ----------
6120
6436
  Kapatınca kod bölmesi gider, Beast Code tam genişlik alır; tercih
6121
6437
  localStorage'da kalıcıdır. Dosya ağacından dosya açınca geri açılır. */
@@ -6207,21 +6523,31 @@ async function renderFileTree() {
6207
6523
  if (!tree) return;
6208
6524
  tree.innerHTML = '';
6209
6525
  const wsLabel = $('#filePanelPath');
6526
+ /* Studio modunda ağaç studioRoot'tan, IDE modunda ideRoot'tan okunur —
6527
+ aynı panel, aynı çizim; yalnız kaynak ve davranış moduna göre değişir */
6528
+ const studio = studioModeOn();
6529
+ const st = studio ? studioState : ideState;
6530
+ const treeApi = studio ? beast.studioTree : beast.ideTree;
6210
6531
  const build = async (rel, depth, container) => {
6211
- let entries = ideState.cache.get(rel);
6532
+ let entries = st.cache.get(rel);
6212
6533
  if (!entries) {
6213
- const r = await beast.ideTree(rel).catch(() => null);
6534
+ const r = await treeApi(rel).catch(() => null);
6214
6535
  if (!r || !r.ok) {
6215
6536
  if (!rel) container.innerHTML = `<div class="file-empty">${escapeHtml((r && r.error) || 'okunamadı')}</div>`;
6216
6537
  return;
6217
6538
  }
6218
6539
  entries = r.entries;
6219
- ideState.cache.set(rel, entries);
6540
+ st.cache.set(rel, entries);
6220
6541
  if (r.workspace) {
6221
- ideState.path = r.workspace;
6542
+ st.path = r.workspace;
6222
6543
  if (wsLabel) { wsLabel.textContent = r.workspace; wsLabel.title = r.workspace; }
6223
- if (els.bcCwd) { els.bcCwd.textContent = r.workspace; els.bcCwd.title = r.workspace; }
6224
- bcOnFolderChanged(r.workspace);
6544
+ if (studio) {
6545
+ if (els.stCwd) { els.stCwd.textContent = r.workspace; els.stCwd.title = r.workspace; }
6546
+ stOnFolderChanged(r.workspace);
6547
+ } else {
6548
+ if (els.bcCwd) { els.bcCwd.textContent = r.workspace; els.bcCwd.title = r.workspace; }
6549
+ bcOnFolderChanged(r.workspace);
6550
+ }
6225
6551
  }
6226
6552
  }
6227
6553
  for (const e of entries) {
@@ -6229,16 +6555,21 @@ async function renderFileTree() {
6229
6555
  const row = document.createElement('div');
6230
6556
  row.className = 'file-row';
6231
6557
  row.style.paddingLeft = 6 + depth * 14 + 'px';
6232
- const ico = e.dir ? (ideState.open.has(child) ? '📂' : '📁') : IDE_ICONS[e.name.slice(e.name.lastIndexOf('.')).toLowerCase()] || '📄';
6558
+ const ext = e.name.slice(e.name.lastIndexOf('.')).toLowerCase();
6559
+ const ico = e.dir ? (st.open.has(child) ? '📂' : '📁') : IDE_ICONS[ext] || '📄';
6233
6560
  row.innerHTML =
6234
6561
  `<span class="f-ico">${ico}</span>` +
6235
6562
  `<span class="f-nm" title="${escapeHtml(e.name)}">${escapeHtml(e.name)}</span>` +
6236
6563
  (e.dir ? '' : `<span class="f-sz">${fmtFileSize(e.size)}</span>`);
6237
6564
  row.addEventListener('click', () => {
6238
6565
  if (e.dir) {
6239
- if (ideState.open.has(child)) ideState.open.delete(child);
6240
- else ideState.open.add(child);
6566
+ if (st.open.has(child)) st.open.delete(child);
6567
+ else st.open.add(child);
6241
6568
  renderFileTree();
6569
+ } else if (studio && MEDIA_FILE_RE.test(e.name)) {
6570
+ stVideoPlay(child); /* video/ses → sağdaki video bölümünde oynat */
6571
+ } else if (studio) {
6572
+ toast('Studio modunda medya dosyaları oynatılır — dosya düzenleme Beast Code modunda');
6242
6573
  } else {
6243
6574
  codeOpen(child);
6244
6575
  }
@@ -6246,7 +6577,7 @@ async function renderFileTree() {
6246
6577
  row.dataset.rel = child;
6247
6578
  row.dataset.dir = e.dir ? '1' : '0';
6248
6579
  container.appendChild(row);
6249
- if (e.dir && ideState.open.has(child)) {
6580
+ if (e.dir && st.open.has(child)) {
6250
6581
  const box = document.createElement('div');
6251
6582
  container.appendChild(box);
6252
6583
  await build(child, depth + 1, box);
@@ -6265,7 +6596,9 @@ function ideRefreshTree() {
6265
6596
  ideTreeTimer = setTimeout(() => {
6266
6597
  ideTreeTimer = null;
6267
6598
  try {
6268
- ideState.cache.clear();
6599
+ /* hangi moddaysak o ağacın önbelleği tazelenir */
6600
+ if (studioModeOn()) studioState.cache.clear();
6601
+ else ideState.cache.clear();
6269
6602
  renderFileTree().catch(() => {});
6270
6603
  } catch {}
6271
6604
  }, 600);
@@ -6658,16 +6991,29 @@ async function codeReloadIfOpen(p) {
6658
6991
  }
6659
6992
 
6660
6993
  $('#ideBtn').addEventListener('click', () => setIdeMode(!ideModeOn()));
6994
+ if (els.studioBtn) els.studioBtn.addEventListener('click', () => setStudioMode(!studioModeOn()));
6661
6995
  $('#filePick').addEventListener('click', async () => {
6662
- const r = await beast.ideSetRoot().catch(() => null);
6996
+ /* Studio modunda klasör seçimi Studio'nun KENDİ kökünü değiştirir —
6997
+ IDE köküne dokunmaz (dünyalar ayrıdır) */
6998
+ const r = studioModeOn()
6999
+ ? await beast.studioSetRoot().catch(() => null)
7000
+ : await beast.ideSetRoot().catch(() => null);
6663
7001
  if (r && r.ok) {
6664
- ideState.open = new Set(['']);
6665
- await loadIdeTree();
7002
+ if (studioModeOn()) {
7003
+ studioState.open = new Set(['']);
7004
+ await loadStudioTree();
7005
+ } else {
7006
+ ideState.open = new Set(['']);
7007
+ await loadIdeTree();
7008
+ }
6666
7009
  } else if (r && !r.canceled && r.error) {
6667
7010
  toast(r.error);
6668
7011
  }
6669
7012
  });
6670
- $('#fileRefresh').addEventListener('click', () => loadIdeTree());
7013
+ $('#fileRefresh').addEventListener('click', () => {
7014
+ if (studioModeOn()) loadStudioTree();
7015
+ else loadIdeTree();
7016
+ });
6671
7017
 
6672
7018
  /* ---------- görev listesi + TEK TUŞ GERİ ALMA (dosya paneli) ---------- */
6673
7019
  let tasksPanelOpen = false;
@@ -6682,11 +7028,14 @@ function toggleTasksPanel(force) {
6682
7028
  async function renderTasksPanel() {
6683
7029
  const box = els.tasksPanel;
6684
7030
  if (!box) return;
6685
- if (!bcSessionId) {
6686
- box.innerHTML = '<div class="tsk-empty">\u00D6nce Beast Code\u2019da bir mesaj yaz — g\u00F6rev listesi i\u00E7in oturum gerekli.</div>';
7031
+ const sid = studioModeOn() ? stSessionId : bcSessionId;
7032
+ if (!sid) {
7033
+ box.innerHTML = '<div class="tsk-empty">' + (studioModeOn()
7034
+ ? '\u00D6nce Beast Studio\u2019da bir mesaj yaz — g\u00F6rev listesi i\u00E7in oturum gerekli.'
7035
+ : '\u00D6nce Beast Code\u2019da bir mesaj yaz — g\u00F6rev listesi i\u00E7in oturum gerekli.') + '</div>';
6687
7036
  return;
6688
7037
  }
6689
- const r = await beast.bcTodos(bcSessionId).catch(() => null);
7038
+ const r = await beast.bcTodos(sid).catch(() => null);
6690
7039
  if (!r || !r.ok) {
6691
7040
  box.innerHTML = '<div class="tsk-empty">' + escapeHtml((r && r.error) || 'okunamad\u0131') + '</div>';
6692
7041
  return;
@@ -6722,9 +7071,10 @@ async function renderTasksPanel() {
6722
7071
  }
6723
7072
 
6724
7073
  async function undoTask(id) {
6725
- if (!bcSessionId) return;
7074
+ const sid = studioModeOn() ? stSessionId : bcSessionId;
7075
+ if (!sid) return;
6726
7076
  if (busy) { toast('Ajan \u00E7al\u0131\u015F\u0131yor — \u25A0 ile durdurup \u00F6yle geri al'); return; }
6727
- const r = await beast.bcUndo(bcSessionId, id).catch(() => null);
7077
+ const r = await beast.bcUndo(sid, id).catch(() => null);
6728
7078
  if (r && r.ok) {
6729
7079
  toast('\u21B6 Geri al\u0131nd\u0131 — ' + (r.reverted || 0) + ' dosya \u00F6nceki h\u00E2line d\u00F6nd\u00FC');
6730
7080
  ideRefreshTree();
@@ -6823,17 +7173,23 @@ function fileCtxShow(e, rel, isDir) {
6823
7173
  const menu = els.fileCtxMenu;
6824
7174
  if (!menu || !rel) return;
6825
7175
  e.preventDefault();
6826
- const isHtml = /\.html?$/i.test(rel);
7176
+ const studio = studioModeOn();
7177
+ const st = studio ? studioState : ideState;
7178
+ const isMedia = MEDIA_FILE_RE.test(rel.split('/').pop() || '');
6827
7179
  const items = [];
6828
7180
  if (isDir) {
6829
7181
  items.push({
6830
- label: ideState.open.has(rel) ? 'Kapat' : 'Aç',
7182
+ label: st.open.has(rel) ? 'Kapat' : 'Aç',
6831
7183
  fn: () => {
6832
- if (ideState.open.has(rel)) ideState.open.delete(rel);
6833
- else ideState.open.add(rel);
7184
+ if (st.open.has(rel)) st.open.delete(rel);
7185
+ else st.open.add(rel);
6834
7186
  renderFileTree();
6835
7187
  },
6836
7188
  });
7189
+ } else if (studio && isMedia) {
7190
+ items.push({ label: 'Oynat', fn: () => stVideoPlay(rel) });
7191
+ } else if (studio) {
7192
+ items.push({ label: 'Dış uygulamada aç', fn: () => beast.openPath((studioState.path || '') + '\\' + rel.replace(/\//g, '\\')).catch(() => {}) });
6837
7193
  } else {
6838
7194
  items.push({ label: 'Aç', fn: () => codeOpen(rel) });
6839
7195
  if (isHtml) {
@@ -6850,13 +7206,17 @@ function fileCtxShow(e, rel, isDir) {
6850
7206
  label: 'Sil',
6851
7207
  danger: true,
6852
7208
  fn: async () => {
6853
- const r = await beast.ideDelete(rel).catch(() => null);
7209
+ const r = studio
7210
+ ? await beast.studioDelete(rel).catch(() => null)
7211
+ : await beast.ideDelete(rel).catch(() => null);
6854
7212
  if (r && r.ok) {
6855
- /* silinen dosya/klasörün açık sekmelerini kapat */
6856
- for (let i = codeTabs.length - 1; i >= 0; i--) {
6857
- if (codeTabs[i].rel === rel || codeTabs[i].rel.startsWith(rel + '/')) codeClose(i);
7213
+ if (!studio) {
7214
+ /* silinen dosya/klasörün açık sekmelerini kapat */
7215
+ for (let i = codeTabs.length - 1; i >= 0; i--) {
7216
+ if (codeTabs[i].rel === rel || codeTabs[i].rel.startsWith(rel + '/')) codeClose(i);
7217
+ }
6858
7218
  }
6859
- await loadIdeTree();
7219
+ await renderFileTree();
6860
7220
  } else if (r && !r.canceled && r.error) {
6861
7221
  toast(r.error);
6862
7222
  }
@@ -6890,12 +7250,113 @@ if (els.fileCtxMenu) {
6890
7250
  $('#fileTree').addEventListener('contextmenu', (e) => {
6891
7251
  const row = e.target.closest('.file-row');
6892
7252
  if (!row || !row.dataset.rel) {
7253
+ /* boş ağaç alanı: hızlı işlemler (mod-farkında) */
7254
+ e.preventDefault();
6893
7255
  fileCtxHide();
7256
+ const menu = els.fileCtxMenu;
7257
+ if (!menu) return;
7258
+ const items = [{ label: 'Yenile', fn: () => (studioModeOn() ? loadStudioTree() : loadIdeTree()) }];
7259
+ const pick = document.getElementById('filePick');
7260
+ if (pick) items.push({ label: 'Klasör seç…', fn: () => pick.click() });
7261
+ menu.innerHTML = '';
7262
+ for (const it of items) ctxMenuAppend(menu, it);
7263
+ menu.hidden = false;
7264
+ const mh = items.length * 33 + 8;
7265
+ menu.style.left = Math.max(4, Math.min(e.clientX, window.innerWidth - 170)) + 'px';
7266
+ menu.style.top = Math.max(4, Math.min(e.clientY, window.innerHeight - mh)) + 'px';
6894
7267
  return;
6895
7268
  }
6896
7269
  fileCtxShow(e, row.dataset.rel, row.dataset.dir === '1');
6897
7270
  });
6898
7271
 
7272
+ /* ---------- UYGULAMA GENELİ SAĞ TIK ----------
7273
+ Metin alanlarında (input/textarea): Kes / Kopyala / Yapıştır / Tümünü seç;
7274
+ seçili metinde (sohbet çıktısı vs.): Kopyala. Pano işlemleri pano
7275
+ köprüsüyle (clip:read / clip:write) MANUEL yapılır — Chromium'un
7276
+ execCommand('paste') kısıtı Electron'da da güvenilir değil; imleç
7277
+ konumuna elle eklenir, 'input' olayı tetiklenir (oto-büyüme vs. çalışır).
7278
+ Dosya ağacı kendi menüsünü gösterir (yukarıda). */
7279
+ function isEditableEl(t) {
7280
+ if (!t || !t.tagName) return false;
7281
+ const tag = String(t.tagName).toLowerCase();
7282
+ if (tag === 'input' || tag === 'textarea') return !(t.disabled || t.readOnly);
7283
+ return false;
7284
+ }
7285
+
7286
+ function ctxMenuAppend(menu, it) {
7287
+ const el = document.createElement('div');
7288
+ el.className = 'ctx-item' + (it.danger ? ' danger' : '') + (it.disabled ? ' disabled' : '');
7289
+ el.textContent = it.label;
7290
+ if (!it.disabled) el.addEventListener('click', () => { fileCtxHide(); it.fn(); });
7291
+ menu.appendChild(el);
7292
+ }
7293
+
7294
+ function ctxFieldInputEvent(el) {
7295
+ try { el.dispatchEvent(new Event('input', { bubbles: true })); } catch {}
7296
+ }
7297
+
7298
+ async function ctxFieldOp(el, op) {
7299
+ if (!el) return;
7300
+ el.focus();
7301
+ if (op === 'all') {
7302
+ if (typeof el.select === 'function') el.select();
7303
+ return;
7304
+ }
7305
+ const v = typeof el.value === 'string' ? el.value : '';
7306
+ const start = typeof el.selectionStart === 'number' ? el.selectionStart : v.length;
7307
+ const end = typeof el.selectionEnd === 'number' ? el.selectionEnd : v.length;
7308
+ if (op === 'paste') {
7309
+ const text = String((await beast.clipRead().catch(() => '')) || '');
7310
+ if (!text) return;
7311
+ el.value = v.slice(0, start) + text + v.slice(end);
7312
+ el.setSelectionRange(start + text.length, start + text.length);
7313
+ ctxFieldInputEvent(el);
7314
+ return;
7315
+ }
7316
+ if (op === 'copy' || op === 'cut') {
7317
+ const sel = v.slice(start, end);
7318
+ if (!sel) return;
7319
+ await beast.clipWrite(sel).catch(() => {});
7320
+ if (op === 'cut') {
7321
+ el.value = v.slice(0, start) + v.slice(end);
7322
+ el.setSelectionRange(start, start);
7323
+ ctxFieldInputEvent(el);
7324
+ }
7325
+ }
7326
+ }
7327
+
7328
+ document.addEventListener('contextmenu', (e) => {
7329
+ /* dosya ağacı kendi menüsünü kendisi açar */
7330
+ if (e.target && e.target.closest && e.target.closest('#fileTree')) return;
7331
+ fileCtxHide();
7332
+ const field = isEditableEl(e.target) ? e.target : (isEditableEl(document.activeElement) ? document.activeElement : null);
7333
+ const selText = String((window.getSelection && window.getSelection()) || '');
7334
+ const hasSel = field && typeof field.selectionStart === 'number'
7335
+ ? field.selectionEnd > field.selectionStart
7336
+ : !!selText.trim();
7337
+ if (!field && !hasSel) return; /* boş alan — menü yok */
7338
+ e.preventDefault();
7339
+ const menu = els.fileCtxMenu;
7340
+ if (!menu) return;
7341
+ const items = [];
7342
+ if (field) items.push({ label: 'Kes', disabled: !hasSel, fn: () => ctxFieldOp(field, 'cut') });
7343
+ items.push({
7344
+ label: 'Kopyala',
7345
+ disabled: !hasSel,
7346
+ fn: () => (field ? ctxFieldOp(field, 'copy') : beast.clipWrite(selText).catch(() => {})),
7347
+ });
7348
+ if (field) {
7349
+ items.push({ label: 'Yapıştır', fn: () => ctxFieldOp(field, 'paste') });
7350
+ items.push({ label: 'Tümünü seç', fn: () => ctxFieldOp(field, 'all') });
7351
+ }
7352
+ menu.innerHTML = '';
7353
+ for (const it of items) ctxMenuAppend(menu, it);
7354
+ menu.hidden = false;
7355
+ const mh = items.length * 33 + 8;
7356
+ menu.style.left = Math.max(4, Math.min(e.clientX, window.innerWidth - 170)) + 'px';
7357
+ menu.style.top = Math.max(4, Math.min(e.clientY, window.innerHeight - mh)) + 'px';
7358
+ });
7359
+
6899
7360
  /* ---------- IDE modu ortası: BEAST CODE paneli ----------
6900
7361
  Sohbet yerine Beast'in kendi ajanı (varsayılan model) çalışır;
6901
7362
  soldaki dosya panelindeki klasörü workspace olarak kullanır.
@@ -7385,5 +7846,399 @@ if (els.bcNew) els.bcNew.addEventListener('click', async () => {
7385
7846
  bcLine('t-sys', 'yeni oturum — sonraki mesaj taze başlar');
7386
7847
  }
7387
7848
  });
7849
+
7850
+ /* ---------- BEAST STUDIO paneli ----------
7851
+ Beast Code panelinin birebir karşılığı — AMA ayrı oturum, ayrı kuyruk,
7852
+ ayrı çıktı. Olaylar stSessionId ile eşlenir; Beast Code sohbetine ve ana
7853
+ chate ASLA sızmaz. Ajan buildStudioSystem ile açılır: video yapma/düzenleme. */
7854
+
7855
+ const ST_MAX_LINES = 1200;
7856
+ let stSessionId = null;
7857
+ let stRunning = false;
7858
+ let stBannerDone = false;
7859
+ let stStreamEl = null;
7860
+ let stStreamRaw = '';
7861
+ let stWsPath = '';
7862
+ const stTools = new Map(); /* callId → { box, sec } */
7863
+ let stCurBox = null;
7864
+ let stNoteEl = null;
7865
+ let stNoteRaw = '';
7866
+
7867
+ function stScroll() {
7868
+ const b = els.stOut;
7869
+ if (!b) return;
7870
+ b.scrollTop = b.scrollHeight;
7871
+ }
7872
+
7873
+ function stBodyScroll(el) {
7874
+ const b = el && el.closest ? el.closest('.bc-toolbody') : null;
7875
+ if (b) b.scrollTop = b.scrollHeight;
7876
+ }
7877
+
7878
+ function stLine(cls, text, time = false) {
7879
+ if (!els.stOut) return;
7880
+ const el = document.createElement('div');
7881
+ el.className = 't-line ' + (cls || '');
7882
+ const ts = time ? '<span class="t-time">' + new Date().toTimeString().slice(0, 8) + '</span> ' : '';
7883
+ el.innerHTML = ts + escapeHtml(String(text ?? ''));
7884
+ els.stOut.appendChild(el);
7885
+ while (els.stOut.childElementCount > ST_MAX_LINES) els.stOut.removeChild(els.stOut.firstChild);
7886
+ stScroll();
7887
+ }
7888
+
7889
+ function stBanner() {
7890
+ if (stBannerDone) return;
7891
+ stBannerDone = true;
7892
+ const cwd = studioState.path || '';
7893
+ if (els.stCwd && cwd) { els.stCwd.textContent = cwd; els.stCwd.title = cwd; }
7894
+ stLine('t-sys', 'BEAST STUDIO — stüdyo klasörü: ' + (cwd || '?'));
7895
+ stLine('t-dim', 'Video yapma/düzenleme ajanı: montaj, kesme, altyazı, ses, format — ffmpeg ile üretir, çıktılar "output" klasörüne düşer. + yeni oturum · Temizle çıktıyı siler.', false);
7896
+ }
7897
+
7898
+ /* Klasör değişimi → panel taze başlar (oturum main'de klasör bazlı saklanır) */
7899
+ function stOnFolderChanged(p) {
7900
+ if (!p || stWsPath === p) return;
7901
+ const first = !stWsPath;
7902
+ stWsPath = p;
7903
+ if (first) return; /* ilk yükleme — panel zaten boş, banner setStudioMode'da basılır */
7904
+ if (els.stOut) els.stOut.innerHTML = '';
7905
+ stTools.clear();
7906
+ stCloseToolGroup();
7907
+ stHideTodos();
7908
+ stStreamEl = null;
7909
+ stStreamRaw = '';
7910
+ stBannerDone = false;
7911
+ stBanner();
7912
+ }
7913
+
7914
+ function stSetBusy(v) {
7915
+ stRunning = !!v;
7916
+ if (els.stStop) els.stStop.hidden = !v;
7917
+ }
7918
+
7919
+ function stFlushStream() {
7920
+ stStreamEl = null;
7921
+ stStreamRaw = '';
7922
+ }
7923
+
7924
+ function stStreamDelta(delta) {
7925
+ if (!els.stOut) return;
7926
+ if (!stStreamEl) {
7927
+ stStreamEl = document.createElement('div');
7928
+ stStreamEl.className = 't-line t-out';
7929
+ els.stOut.appendChild(stStreamEl);
7930
+ }
7931
+ stStreamRaw += String(delta || '');
7932
+ stStreamEl.textContent = stStreamRaw.slice(-4000);
7933
+ stScroll(true);
7934
+ }
7935
+
7936
+ function stStatusShow(text) {
7937
+ if (!els.stStatus) return;
7938
+ const t = String(text || '').trim();
7939
+ if (!t) return;
7940
+ els.stStatus.textContent = t;
7941
+ els.stStatus.hidden = false;
7942
+ }
7943
+
7944
+ function stStatusHide() {
7945
+ if (els.stStatus) els.stStatus.hidden = true;
7946
+ }
7947
+
7948
+ function stNoteDetach() {
7949
+ stNoteEl = null;
7950
+ stNoteRaw = '';
7951
+ }
7952
+
7953
+ function stCloseToolGroup() {
7954
+ if (stCurBox) stCurBox.classList.remove('running');
7955
+ stCurBox = null;
7956
+ stNoteDetach();
7957
+ }
7958
+
7959
+ function stToolGroupNew() {
7960
+ const box = document.createElement('div');
7961
+ box.className = 'bc-toolbox';
7962
+ const body = document.createElement('div');
7963
+ body.className = 'bc-toolbody';
7964
+ box.appendChild(body);
7965
+ els.stOut.appendChild(box);
7966
+ while (els.stOut.childElementCount > ST_MAX_LINES) els.stOut.removeChild(els.stOut.firstChild);
7967
+ return box;
7968
+ }
7969
+
7970
+ function stToolBoxStart(callId, name, args) {
7971
+ stFlushStream();
7972
+ if (!stCurBox) stCurBox = stToolGroupNew();
7973
+ const sec = document.createElement('div');
7974
+ sec.className = 'bc-toolsection';
7975
+ const short = termShortTool(name, args);
7976
+ sec.innerHTML =
7977
+ '<div class="bc-toolhead">' +
7978
+ '<span class="bc-toolspin"></span>' +
7979
+ '<span class="bc-toolname">▸ ' + escapeHtml(String(name || 'araç')) + '</span>' +
7980
+ (short ? '<span class="bc-toolar" title="' + escapeHtml(short) + '">' + escapeHtml(short) + '</span>' : '') +
7981
+ '</div>' +
7982
+ '<div class="bc-toolout" hidden></div>';
7983
+ stCurBox.classList.add('running');
7984
+ const body = stCurBox.querySelector('.bc-toolbody') || stCurBox;
7985
+ body.appendChild(sec);
7986
+ if (callId) stTools.set(callId, { box: stCurBox, sec });
7987
+ stScroll();
7988
+ stBodyScroll(body);
7989
+ }
7990
+
7991
+ function stToolBoxEnd(callId, ok, result, diff) {
7992
+ stFlushStream();
7993
+ const t = (callId && stTools.get(callId)) || null;
7994
+ if (callId) stTools.delete(callId);
7995
+ const out = String(result || '').replace(/\s+$/, '');
7996
+ if (t) {
7997
+ const { sec } = t;
7998
+ sec.classList.add('done');
7999
+ if (!ok) sec.classList.add('failed');
8000
+ if (!t.box.querySelector('.bc-toolsection:not(.done)')) {
8001
+ t.box.classList.remove('running');
8002
+ }
8003
+ const pre = sec.querySelector('.bc-toolout');
8004
+ if (diff && diff.path) {
8005
+ const headEl = sec.querySelector('.bc-toolhead');
8006
+ if (headEl && !headEl.querySelector('.diff-badge')) {
8007
+ const badge = document.createElement('span');
8008
+ badge.className = 'diff-badge';
8009
+ const a = document.createElement('span');
8010
+ a.className = 'diff-badge-add';
8011
+ a.textContent = '+' + (diff.additions || 0);
8012
+ const d = document.createElement('span');
8013
+ d.className = 'diff-badge-del';
8014
+ d.textContent = '−' + (diff.deletions || 0);
8015
+ badge.appendChild(a);
8016
+ badge.appendChild(d);
8017
+ headEl.appendChild(badge);
8018
+ }
8019
+ pre.hidden = false;
8020
+ pre.textContent = '';
8021
+ pre.classList.add('bc-diffview');
8022
+ pre.appendChild(buildDiffEl(diff, pre.clientWidth || 480));
8023
+ } else if (out) {
8024
+ pre.hidden = false;
8025
+ pre.textContent = out.length > 4000 ? out.slice(0, 4000) + '\n… (kesildi)' : out;
8026
+ } else {
8027
+ pre.remove();
8028
+ }
8029
+ stBodyScroll(sec);
8030
+ } else if (out || !ok) {
8031
+ stLine(ok ? 't-out' : 't-err', out ? (out.length > 400 ? out.slice(0, 400) + ' …(kesildi)' : out) : '(hata)');
8032
+ }
8033
+ stScroll();
8034
+ }
8035
+
8036
+ /* Ara yazı → açık grup varsa kutu İÇİNE not olarak akar */
8037
+ function stNoteStream(delta) {
8038
+ if (!stCurBox) return false;
8039
+ if (!stNoteEl) {
8040
+ stNoteEl = document.createElement('div');
8041
+ stNoteEl.className = 'bc-toolnote';
8042
+ const body = stCurBox.querySelector('.bc-toolbody') || stCurBox;
8043
+ body.appendChild(stNoteEl);
8044
+ }
8045
+ stNoteRaw += String(delta || '');
8046
+ stNoteEl.textContent = stNoteRaw.slice(-2000);
8047
+ stScroll(true);
8048
+ stBodyScroll(stNoteEl);
8049
+ return true;
8050
+ }
8051
+
8052
+ function stRenderTodos(todos) {
8053
+ const list = Array.isArray(todos) ? todos : [];
8054
+ if (!els.stTodoWrap) return;
8055
+ stFlushStream();
8056
+ if (!list.length) {
8057
+ stHideTodos();
8058
+ return;
8059
+ }
8060
+ els.stTodoWrap.hidden = false;
8061
+ els.stTodoWrap.innerHTML =
8062
+ '<div class="bc-todobox">' +
8063
+ '<div class="bc-todotitle"><span>GÖREVLER</span><span class="bc-todocount"></span></div>' +
8064
+ '<div class="bc-todoitems"></div>';
8065
+ const done = list.filter((t) => t.status === 'done').length;
8066
+ els.stTodoWrap.querySelector('.bc-todocount').textContent = done + '/' + list.length;
8067
+ const wrap = els.stTodoWrap.querySelector('.bc-todoitems');
8068
+ wrap.innerHTML = '';
8069
+ for (const t of list) {
8070
+ const st = TODO_GLYPH[t.status] ? t.status : 'pending';
8071
+ const row = document.createElement('div');
8072
+ row.className = 'bc-todoitem ' + st;
8073
+ row.innerHTML =
8074
+ '<span class="bc-todocheck">' + TODO_GLYPH[st] + '</span>' +
8075
+ '<span class="bc-todotext"></span>';
8076
+ row.querySelector('.bc-todotext').textContent = String(t.title || '');
8077
+ wrap.appendChild(row);
8078
+ }
8079
+ }
8080
+
8081
+ function stHideTodos() {
8082
+ if (els.stTodoWrap) {
8083
+ els.stTodoWrap.hidden = true;
8084
+ els.stTodoWrap.innerHTML = '';
8085
+ }
8086
+ }
8087
+
8088
+ /* Studio oturumunun engine olayları → panel çıktısı (bcIngest'in karşılığı) */
8089
+ function stIngest(ev) {
8090
+ switch (ev.type) {
8091
+ case 'token':
8092
+ if (stNoteStream(ev.delta)) break;
8093
+ stStreamDelta(ev.delta);
8094
+ break;
8095
+ case 'message':
8096
+ if (ev.message && ev.message.role === 'assistant') {
8097
+ const content = typeof ev.message.content === 'string' ? ev.message.content : '';
8098
+ const hasTools = Array.isArray(ev.message.tool_calls) && ev.message.tool_calls.length > 0;
8099
+ if (hasTools) {
8100
+ stNoteDetach();
8101
+ stFlushStream();
8102
+ break;
8103
+ }
8104
+ if (stNoteEl) { stNoteEl.remove(); stNoteDetach(); }
8105
+ if (content && !stStreamRaw.trim()) stLine('t-out', content);
8106
+ stCloseToolGroup();
8107
+ stFlushStream();
8108
+ }
8109
+ break;
8110
+ case 'todos':
8111
+ stRenderTodos(ev.todos);
8112
+ break;
8113
+ case 'tool-start':
8114
+ stSetBusy(true);
8115
+ stToolBoxStart(ev.callId, ev.name, ev.args);
8116
+ break;
8117
+ case 'tool-end':
8118
+ stToolBoxEnd(ev.callId, ev.ok, ev.result, ev.diff);
8119
+ if (ev.name === 'write_file' || ev.name === 'edit_file' || ev.name === 'run_command' || ev.name === 'python_run') {
8120
+ ideRefreshTree(); /* ffmpeg çıktısı anında ağaca düşer */
8121
+ }
8122
+ break;
8123
+ case 'st-mode':
8124
+ if (els.stTitle) {
8125
+ els.stTitle.textContent =
8126
+ ev.mode === 'plan' ? 'BEAST STUDIO · PLAN' :
8127
+ ev.mode === 'build' ? 'BEAST STUDIO · BUILD' : 'BEAST STUDIO';
8128
+ }
8129
+ stLine('t-dim', '[' + (ev.body || ev.mode || 'mod değişti') + ']');
8130
+ break;
8131
+ case 'done':
8132
+ stFlushStream();
8133
+ stCloseToolGroup();
8134
+ stSetBusy(false);
8135
+ stStatusHide();
8136
+ stLine(ev.aborted ? 't-err' : 't-dim', ev.aborted ? '[durduruldu — sebep: ' + (ev.reason || 'sebep belirtilmedi') + ']' : '(tamamlandı)');
8137
+ ideRefreshTree();
8138
+ if (studioModeOn() && els.stInput) els.stInput.focus();
8139
+ break;
8140
+ case 'error':
8141
+ stFlushStream();
8142
+ stCloseToolGroup();
8143
+ stSetBusy(false);
8144
+ stStatusHide();
8145
+ stLine('t-err', '[hata] ' + (ev.error || ''));
8146
+ break;
8147
+ case 'status':
8148
+ if (ev.status === 'idle') {
8149
+ stCloseToolGroup();
8150
+ stSetBusy(false);
8151
+ stStatusHide();
8152
+ } else {
8153
+ stSetBusy(true);
8154
+ stStatusShow(ev.status === 'thinking' ? 'düşünüyor…' : String(ev.status || ''));
8155
+ }
8156
+ break;
8157
+ }
8158
+ }
8159
+
8160
+ function stInputResize() {
8161
+ const ta = els.stInput;
8162
+ if (!ta) return;
8163
+ ta.style.height = 'auto';
8164
+ const max = 140;
8165
+ const h = Math.min(ta.scrollHeight, max);
8166
+ ta.style.height = h + 'px';
8167
+ ta.classList.toggle('expand', ta.scrollHeight > max);
8168
+ }
8169
+
8170
+ function stRunCurrent() {
8171
+ const msg = els.stInput.value.trim();
8172
+ if (!msg && !stPending.length) return;
8173
+ const atts = stPending.slice();
8174
+ stPending = [];
8175
+ renderStChips();
8176
+ els.stInput.value = '';
8177
+ stInputResize();
8178
+ els.stInput.focus();
8179
+ stLine('t-cmd', 'studio> ' + (msg || (atts.length ? '[' + atts.length + ' ek]' : '')));
8180
+ stHideTodos();
8181
+ stSetBusy(true);
8182
+ beast.studioSend(msg || '[dosya ekleri]', atts).then((r) => {
8183
+ if (r && r.ok && r.mode) {
8184
+ if (r.sessionId) stSessionId = r.sessionId;
8185
+ stSetBusy(false);
8186
+ } else if (r && r.ok && r.queued) {
8187
+ if (r.sessionId) stSessionId = r.sessionId;
8188
+ stLine('t-dim', '⏳ kuyrukta (' + r.count + ') — iş bitince gönderilir');
8189
+ } else if (r && r.ok && r.pending) {
8190
+ if (r.sessionId) stSessionId = r.sessionId;
8191
+ } else if (r && r.ok) {
8192
+ if (r.sessionId) stSessionId = r.sessionId;
8193
+ } else if (r && r.busy) {
8194
+ stLine('t-dim', (r && r.error) || 'mesaj sürüyor');
8195
+ } else {
8196
+ stSetBusy(false);
8197
+ stLine('t-err', (r && r.error) || 'gönderilemedi');
8198
+ }
8199
+ }).catch((e) => {
8200
+ stSetBusy(false);
8201
+ stLine('t-err', String((e && e.message) || e));
8202
+ });
8203
+ }
8204
+
8205
+ if (els.stInput) {
8206
+ els.stInput.addEventListener('input', stInputResize);
8207
+ els.stInput.addEventListener('keydown', (e) => {
8208
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); stRunCurrent(); }
8209
+ });
8210
+ }
8211
+ if (els.stStop) els.stStop.addEventListener('click', () => {
8212
+ stPending = [];
8213
+ renderStChips();
8214
+ beast.studioStop().then((r) => {
8215
+ if (r && r.ok && r.wasBusy === false) {
8216
+ stSetBusy(false);
8217
+ stStatusHide();
8218
+ }
8219
+ }).catch(() => {});
8220
+ });
8221
+ if (els.stClear) els.stClear.addEventListener('click', () => {
8222
+ els.stOut.innerHTML = '';
8223
+ stTools.clear();
8224
+ stCloseToolGroup();
8225
+ stHideTodos();
8226
+ stFlushStream();
8227
+ });
8228
+ if (els.stNew) els.stNew.addEventListener('click', async () => {
8229
+ if (stRunning) { toast('Mesaj sürüyor — önce ■ ile durdur'); return; }
8230
+ const r = await beast.studioNew().catch(() => null);
8231
+ if (r && r.ok) {
8232
+ stSessionId = null;
8233
+ stPending = [];
8234
+ renderStChips();
8235
+ stTools.clear();
8236
+ stCloseToolGroup();
8237
+ stHideTodos();
8238
+ stFlushStream();
8239
+ if (els.stTitle) els.stTitle.textContent = 'BEAST STUDIO';
8240
+ stLine('t-sys', 'yeni oturum — sonraki mesaj taze başlar');
8241
+ }
8242
+ });
7388
8243
  /* Uygulama her açılışta agent (chat) modunda başlar — IDE modu yalnız
7389
8244
  kullanıcı butona basınca girilir (oturumluk). */