beast-agent 0.25.3 → 0.26.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/package.json +2 -1
- package/src/agent/llm.js +17 -1
- package/src/main.js +328 -63
- package/src/preload.js +5 -0
- package/src/renderer/i18n.js +54 -0
- package/src/renderer/index.html +2 -0
- package/src/renderer/renderer.js +129 -2
- package/src/renderer/style.css +59 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beast-agent",
|
|
3
3
|
"productName": "Beast Agent",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.26.0",
|
|
5
5
|
"description": "Ultra-fast local agent shell for Windows.",
|
|
6
6
|
"author": "algokodcom (AlgoKod)",
|
|
7
7
|
"license": "MIT",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"pdfkit": "^0.20.1",
|
|
55
55
|
"qrcode": "^1.5.4",
|
|
56
56
|
"tesseract.js": "^7.0.0",
|
|
57
|
+
"undici": "^7.29.0",
|
|
57
58
|
"ws": "^8.21.3",
|
|
58
59
|
"electron": "40.10.2"
|
|
59
60
|
},
|
package/src/agent/llm.js
CHANGED
|
@@ -135,6 +135,7 @@ async function streamOnce(sel, body, { signal, onDelta, onRetry } = {}, omitReas
|
|
|
135
135
|
const toolCalls = [];
|
|
136
136
|
let usage = null;
|
|
137
137
|
let finishReason = null;
|
|
138
|
+
let sawDone = false;
|
|
138
139
|
|
|
139
140
|
const reader = res.body.getReader();
|
|
140
141
|
const decoder = new TextDecoder();
|
|
@@ -151,7 +152,8 @@ async function streamOnce(sel, body, { signal, onDelta, onRetry } = {}, omitReas
|
|
|
151
152
|
buf = buf.slice(idx + 1);
|
|
152
153
|
if (!line.startsWith('data:')) continue;
|
|
153
154
|
const data = line.slice(5).trim();
|
|
154
|
-
if (!data
|
|
155
|
+
if (!data) continue;
|
|
156
|
+
if (data === '[DONE]') { sawDone = true; continue; }
|
|
155
157
|
|
|
156
158
|
let json;
|
|
157
159
|
try {
|
|
@@ -185,6 +187,20 @@ async function streamOnce(sel, body, { signal, onDelta, onRetry } = {}, omitReas
|
|
|
185
187
|
}
|
|
186
188
|
}
|
|
187
189
|
|
|
190
|
+
/* YARIDA KESİLEN AKIŞ: stream erişilmeyen bir yerde koptuysa reader sessizce
|
|
191
|
+
done=true döner — yarım metni "tamamlanmış" sanmak yerine işaretle.
|
|
192
|
+
([DONE] geldi ama finish_reason yok = sağlayıcı tuhaflığı → kabul) */
|
|
193
|
+
if (!finishReason && !sawDone) {
|
|
194
|
+
if (toolCalls.length) {
|
|
195
|
+
const e = new Error('cevap akışı yarıda kesildi (bağlantı koptu) — araç çağrısı tamamlanamadı, tekrar deneyin');
|
|
196
|
+
e.partialStream = true;
|
|
197
|
+
throw e;
|
|
198
|
+
}
|
|
199
|
+
if (content) {
|
|
200
|
+
content += '\n\n⚠ _akış yarıda kesildi (bağlantı koptu) — devam etmesini istersen tekrar yaz._';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
188
204
|
return { content, reasoning, toolCalls, usage, finishReason };
|
|
189
205
|
}
|
|
190
206
|
|
package/src/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { app, BrowserWindow, WebContentsView, ipcMain, shell, dialog, Tray, Menu, nativeImage, desktopCapturer, session
|
|
3
|
+
const { app, BrowserWindow, WebContentsView, ipcMain, shell, dialog, Tray, Menu, nativeImage, desktopCapturer, session } = require('electron');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const http = require('http');
|
|
@@ -2327,6 +2327,10 @@ function ensureDesktopShortcut() {
|
|
|
2327
2327
|
}
|
|
2328
2328
|
|
|
2329
2329
|
app.whenReady().then(() => {
|
|
2330
|
+
try { applyProxy(); } catch {} // proxy ayarı yüklüyse tüm çıkışa uygula
|
|
2331
|
+
if (settings.warp && settings.warp.enabled) {
|
|
2332
|
+
startWarp().catch((e) => log.info('main', 'warp açılışta başlatılamadı: ' + String((e && e.message) || e)));
|
|
2333
|
+
}
|
|
2330
2334
|
// Tailscale modu: paketli uygulamada Windows ile otomatik başlat (sessiz, tepside)
|
|
2331
2335
|
if (app.isPackaged) {
|
|
2332
2336
|
/* DAĞITIM KARARI: EXE/portable kurulum desteklenmiyor — tek yol npm.
|
|
@@ -2398,6 +2402,7 @@ app.whenReady().then(() => {
|
|
|
2398
2402
|
app.on('before-quit', () => {
|
|
2399
2403
|
app.isQuitting = true;
|
|
2400
2404
|
flushBrowserStorage(); // x.com/google oturumları (cookies) diske yazılsın
|
|
2405
|
+
try { stopWarp(); } catch {} // warp-plus yardımcısını da kapat
|
|
2401
2406
|
});
|
|
2402
2407
|
|
|
2403
2408
|
if (process.argv.includes('--smoke')) {
|
|
@@ -3743,21 +3748,27 @@ function flushDesktopOnDone(ev) {
|
|
|
3743
3748
|
- Bağlantı geri gelince (DNS kontrolü) sırayla otomatik gönderilir
|
|
3744
3749
|
- Renderer'a 'net' / 'netQueue' olayları gider: ⏳ kuyruk balonu + toast */
|
|
3745
3750
|
const NET_CHECK_HOSTS = ['one.one.one.one', 'dns.google'];
|
|
3746
|
-
const NET_LOOKUP_HOSTS = ['www.google.com', 'www.microsoft.com'];
|
|
3747
|
-
const NET_HTTP_PROBES = [
|
|
3748
|
-
'http://www.msftconnecttest.com/connecttest.txt',
|
|
3749
|
-
'http://cp.cloudflare.com/generate_204',
|
|
3750
|
-
'http://connectivitycheck.gstatic.com/generate_204',
|
|
3751
|
-
];
|
|
3752
3751
|
const NET_CHECK_MS = 8000;
|
|
3753
3752
|
const NET_CHECK_TIMEOUT = 4000;
|
|
3754
|
-
const
|
|
3753
|
+
const NET_PROBE_URLS = ['https://1.1.1.1/cdn-cgi/trace', 'https://www.google.com/generate_204'];
|
|
3754
|
+
let __netFailStreak = 0;
|
|
3755
|
+
|
|
3756
|
+
/* Gerçek HTTP yoklaması: DNS yerine sayfa çekmeyi dener — proxy açıksa proxy
|
|
3757
|
+
üzerinden gider (kullanıcı trafiğinin gördüğü interneti ölçer) */
|
|
3758
|
+
async function httpProbe() {
|
|
3759
|
+
for (const u of NET_PROBE_URLS) {
|
|
3760
|
+
try {
|
|
3761
|
+
const res = await (__origFetch || fetch)(u, { signal: AbortSignal.timeout(5000) });
|
|
3762
|
+
if (res.ok) return true;
|
|
3763
|
+
} catch {}
|
|
3764
|
+
}
|
|
3765
|
+
return false;
|
|
3766
|
+
}
|
|
3755
3767
|
const CHAT_QUEUE_MAX = 50; // kuyruk üst sınırı — taşarsa en eski düşer
|
|
3756
3768
|
|
|
3757
3769
|
let netOnline = true; // son bilinen bağlantı durumu (başlangıçta iyimser)
|
|
3758
3770
|
let netCheckedOnce = false;
|
|
3759
3771
|
let netCheckBusy = false;
|
|
3760
|
-
let netFailStreak = 0;
|
|
3761
3772
|
let chatQueueFlushing = false;
|
|
3762
3773
|
const chatOfflineQueue = []; // { key, sessionId, text, attachments, at }
|
|
3763
3774
|
|
|
@@ -3841,42 +3852,8 @@ async function flushChatQueue() {
|
|
|
3841
3852
|
}
|
|
3842
3853
|
}
|
|
3843
3854
|
|
|
3844
|
-
/* gerçek internet
|
|
3845
|
-
|
|
3846
|
-
ISS DNS engellemelerinde (ör. 1.1.1.1, dns.google) internet VARken bile
|
|
3847
|
-
başarısız çıkar. Bu yüzden katmanlı deniyoruz; HERHANGİ bir katman başarılıysa
|
|
3848
|
-
internet VAR sayılır:
|
|
3849
|
-
1) HTTP connectivity endpoint'leri (http modülü dns.lookup = OS çözümleyicisi
|
|
3850
|
-
kullanır — tarayıcı gibi; captive portal/proxy/mobil ağ hepsinde çalışır)
|
|
3851
|
-
2) dns.lookup (Windows sistem çözümleyicisi — hosts dosyası/VPN/NRPT dahil)
|
|
3852
|
-
+ dns.resolve (doğrudan DNS sunucusu)
|
|
3853
|
-
3) OS'in kendi bağlantı durumu (Electron net.isOnline — Windows NCSI)
|
|
3854
|
-
Ayrıca tek başarısız tur offline ilan etmez (2 üst üste başarısız tur gerekir):
|
|
3855
|
-
geçici DNS gecikmesi mesajları gereksiz kuyruğa atmaz. */
|
|
3856
|
-
function httpProbe(url) {
|
|
3857
|
-
return new Promise((resolve) => {
|
|
3858
|
-
let done = false;
|
|
3859
|
-
const fin = (v) => { if (!done) { done = true; resolve(v); } };
|
|
3860
|
-
try {
|
|
3861
|
-
const req = http.get(url, { timeout: NET_CHECK_TIMEOUT }, (res) => {
|
|
3862
|
-
res.resume(); // gövdeyi tüket — soket serbest kalsın
|
|
3863
|
-
const ok = !!res.statusCode && res.statusCode < 500;
|
|
3864
|
-
try { res.destroy(); } catch {}
|
|
3865
|
-
fin(ok);
|
|
3866
|
-
});
|
|
3867
|
-
req.on('timeout', () => { try { req.destroy(); } catch {} fin(false); });
|
|
3868
|
-
req.on('error', () => fin(false));
|
|
3869
|
-
} catch { fin(false); }
|
|
3870
|
-
});
|
|
3871
|
-
}
|
|
3872
|
-
|
|
3873
|
-
function lookupProbe(host) {
|
|
3874
|
-
return new Promise((resolve) => {
|
|
3875
|
-
const t = setTimeout(() => resolve(false), NET_CHECK_TIMEOUT);
|
|
3876
|
-
dns.lookup(host, (err) => { clearTimeout(t); resolve(!err); });
|
|
3877
|
-
});
|
|
3878
|
-
}
|
|
3879
|
-
|
|
3855
|
+
/* gerçek internet kontrolü: DNS çözümlemesi (sadece ağ arayüzü değil,
|
|
3856
|
+
paket gerçekten çıkıyor mu test eder). Adaylar sırayla denenir. */
|
|
3880
3857
|
function dnsProbe(host) {
|
|
3881
3858
|
return new Promise((resolve) => {
|
|
3882
3859
|
const t = setTimeout(() => resolve(false), NET_CHECK_TIMEOUT);
|
|
@@ -3891,37 +3868,37 @@ async function netCheck() {
|
|
|
3891
3868
|
if (netCheckBusy) return;
|
|
3892
3869
|
netCheckBusy = true;
|
|
3893
3870
|
try {
|
|
3894
|
-
let ok =
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
...NET_LOOKUP_HOSTS.map(lookupProbe),
|
|
3898
|
-
...NET_CHECK_HOSTS.map(dnsProbe),
|
|
3899
|
-
];
|
|
3900
|
-
ok = (await Promise.all(lookups)).some(Boolean);
|
|
3871
|
+
let ok = false;
|
|
3872
|
+
for (const h of NET_CHECK_HOSTS) {
|
|
3873
|
+
if (await dnsProbe(h)) { ok = true; break; }
|
|
3901
3874
|
}
|
|
3902
3875
|
if (!ok) {
|
|
3903
|
-
|
|
3876
|
+
/* DNS probe yanıltıcı olabilir (proxy, DoH, kurumsal ağ, VPN):
|
|
3877
|
+
HTTP yoklamasıyla DOĞRULA — tarayıcının gördüğü internete yakın sinyal */
|
|
3878
|
+
ok = await httpProbe();
|
|
3904
3879
|
}
|
|
3905
3880
|
const first = !netCheckedOnce;
|
|
3906
3881
|
const was = netOnline;
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3882
|
+
/* Histerezis: tek-iki başarısız yoklama (wifi tıkı, anlık dns) durumu
|
|
3883
|
+
bozmasın — üst üste 3 yoklama (≈24+ sn) başarısızsa offline ilan et */
|
|
3884
|
+
if (!ok) {
|
|
3885
|
+
__netFailStreak++;
|
|
3886
|
+
if (netOnline && __netFailStreak < 3) { netCheckedOnce = true; return; }
|
|
3911
3887
|
} else {
|
|
3912
|
-
|
|
3913
|
-
if (!was || netFailStreak >= NET_OFFLINE_STRIKES) netOnline = false;
|
|
3888
|
+
__netFailStreak = 0;
|
|
3914
3889
|
}
|
|
3915
|
-
|
|
3890
|
+
netOnline = ok;
|
|
3891
|
+
netCheckedOnce = true;
|
|
3892
|
+
if (was !== ok || first) {
|
|
3916
3893
|
try {
|
|
3917
|
-
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'net', online:
|
|
3894
|
+
if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'net', online: ok });
|
|
3918
3895
|
} catch {}
|
|
3919
|
-
if (
|
|
3896
|
+
if (ok) {
|
|
3920
3897
|
log.info('main', 'bağlantı geri geldi — offline kuyruk kontrol ediliyor');
|
|
3921
3898
|
chatQueueEmit(); // renderer: pill/toast güncellensin
|
|
3922
3899
|
flushChatQueue().catch(() => {});
|
|
3923
3900
|
} else {
|
|
3924
|
-
log.info('main',
|
|
3901
|
+
log.info('main', 'internet bağlantısı yok — mesajlar kuyruğa alınacak');
|
|
3925
3902
|
chatQueueEmit();
|
|
3926
3903
|
}
|
|
3927
3904
|
}
|
|
@@ -4159,6 +4136,294 @@ ipcMain.handle('wa:groups:set', (_e, cfg) => {
|
|
|
4159
4136
|
return settings.waGroups;
|
|
4160
4137
|
});
|
|
4161
4138
|
|
|
4139
|
+
/* ---------------- Proxy (Ayarlar → Proxy) ----------------
|
|
4140
|
+
Kullanıcının verdiği proxy(ler) üzerinden çıkış: LLM API'leri, http_fetch,
|
|
4141
|
+
TinyFish/Exa, python motorları (env) ve dahili tarayıcı (Chromium proxyRules).
|
|
4142
|
+
Çoklu adres = Node fetch tarafında round-robin rotasyon. SOCKS5 yalnız tarayıcıda. */
|
|
4143
|
+
let __proxyIdx = 0;
|
|
4144
|
+
let __proxyAgents = null; // url -> undici.ProxyAgent
|
|
4145
|
+
let __origFetch = null;
|
|
4146
|
+
|
|
4147
|
+
function proxyUrlList() {
|
|
4148
|
+
return String((settings.proxy && settings.proxy.urls) || '')
|
|
4149
|
+
.split(/[\n,;]+/)
|
|
4150
|
+
.map((s) => s.trim())
|
|
4151
|
+
.filter((s) => /^(https?|socks5):\/\//i.test(s));
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
function proxyAgentFor(url) {
|
|
4155
|
+
const undici = require('undici');
|
|
4156
|
+
if (!__proxyAgents) __proxyAgents = new Map();
|
|
4157
|
+
if (!__proxyAgents.has(url)) {
|
|
4158
|
+
if (/^socks5:/i.test(url)) __proxyAgents.set(url, new undici.Agent({ connect: socks5Connector(url), connections: 64 }));
|
|
4159
|
+
else __proxyAgents.set(url, new undici.ProxyAgent(url));
|
|
4160
|
+
}
|
|
4161
|
+
return __proxyAgents.get(url);
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4164
|
+
/* Minimal SOCKS5 CONNECT connector (undici Agent.connect imzası).
|
|
4165
|
+
WARP (warp-plus) dahil tüm socks5 proxy'leri Node fetch'te çalıştırır. */
|
|
4166
|
+
function socks5Connector(socksUrl) {
|
|
4167
|
+
const net = require('net');
|
|
4168
|
+
const tls = require('tls');
|
|
4169
|
+
const u = new URL(socksUrl);
|
|
4170
|
+
const shost = u.hostname;
|
|
4171
|
+
const sport = Number(u.port) || 1080;
|
|
4172
|
+
const suser = decodeURIComponent(u.username || '');
|
|
4173
|
+
const spass = decodeURIComponent(u.password || '');
|
|
4174
|
+
return function connect(opts, cb) {
|
|
4175
|
+
let stage = 0; // 0 greet · 1 auth · 2 conn
|
|
4176
|
+
let buf = Buffer.alloc(0);
|
|
4177
|
+
const socket = net.connect({ host: shost, port: sport });
|
|
4178
|
+
const fail = (err) => { try { socket.destroy(); } catch {} cb(err); };
|
|
4179
|
+
const onData = (d) => {
|
|
4180
|
+
buf = Buffer.concat([buf, d]);
|
|
4181
|
+
if (stage === 0) {
|
|
4182
|
+
if (buf.length < 2) return;
|
|
4183
|
+
if (buf[0] !== 5) return fail(new Error('socks5: geçersiz yanıt'));
|
|
4184
|
+
const method = buf[1];
|
|
4185
|
+
buf = buf.subarray(2);
|
|
4186
|
+
if (method === 2) {
|
|
4187
|
+
stage = 1;
|
|
4188
|
+
const ub = Buffer.from(suser), pb = Buffer.from(spass);
|
|
4189
|
+
socket.write(Buffer.concat([Buffer.from([1, ub.length]), ub, Buffer.from([pb.length]), pb]));
|
|
4190
|
+
} else if (method === 0) sendReq();
|
|
4191
|
+
else return fail(new Error('socks5: desteklenmeyen kimlik doğrulama'));
|
|
4192
|
+
} else if (stage === 1) {
|
|
4193
|
+
if (buf.length < 2) return;
|
|
4194
|
+
buf = buf.subarray(2);
|
|
4195
|
+
sendReq();
|
|
4196
|
+
} else if (stage === 2) {
|
|
4197
|
+
if (buf.length < 5) return;
|
|
4198
|
+
if (buf[0] !== 5 || buf[1] !== 0) return fail(new Error('socks5: bağlantı reddedildi (' + buf[1] + ')'));
|
|
4199
|
+
const need = 6 + (buf[3] === 1 ? 4 : buf[3] === 4 ? 16 : 1 + buf[4]);
|
|
4200
|
+
if (buf.length < need) return;
|
|
4201
|
+
buf = Buffer.alloc(0);
|
|
4202
|
+
socket.off('data', onData);
|
|
4203
|
+
if (opts.protocol === 'https:') {
|
|
4204
|
+
const tlsSock = tls.connect({ socket, servername: opts.servername || opts.hostname });
|
|
4205
|
+
tlsSock.on('error', fail);
|
|
4206
|
+
tlsSock.once('secureConnect', () => cb(null, tlsSock));
|
|
4207
|
+
} else cb(null, socket);
|
|
4208
|
+
}
|
|
4209
|
+
};
|
|
4210
|
+
socket.on('error', fail);
|
|
4211
|
+
socket.on('connect', () => {
|
|
4212
|
+
/* SOCKS5 greeting: şifre varsa user/pass(2), yoksa no-auth(0) yöntemi öner */
|
|
4213
|
+
socket.write(suser ? Buffer.from([5, 2, 0, 2]) : Buffer.from([5, 1, 0]));
|
|
4214
|
+
});
|
|
4215
|
+
socket.on('data', onData);
|
|
4216
|
+
function sendReq() {
|
|
4217
|
+
stage = 2;
|
|
4218
|
+
const host = opts.hostname || opts.host;
|
|
4219
|
+
const port = Number(opts.port) || (opts.protocol === 'https:' ? 443 : 80);
|
|
4220
|
+
const hb = Buffer.from(String(host), 'utf8');
|
|
4221
|
+
socket.write(Buffer.concat([Buffer.from([5, 1, 0, 3, hb.length]), hb, Buffer.from([(port >> 8) & 255, port & 255])]));
|
|
4222
|
+
}
|
|
4223
|
+
};
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
function applyProxy() {
|
|
4227
|
+
const enabled = !!(settings.proxy && settings.proxy.enabled);
|
|
4228
|
+
const allUrls = enabled ? proxyUrlList() : [];
|
|
4229
|
+
const httpUrls = allUrls.filter((u) => /^https?:\/\//i.test(u));
|
|
4230
|
+
|
|
4231
|
+
/* 1) Node fetch (LLM API + http_fetch + TinyFish/Exa + GitHub...) — global sarmalayıcı.
|
|
4232
|
+
http(s) VE socks5 (socks5Connector) destekli — http(s) proxy'lerde rotasyon var. */
|
|
4233
|
+
if (enabled && allUrls.length) {
|
|
4234
|
+
if (!__origFetch) {
|
|
4235
|
+
__origFetch = globalThis.fetch;
|
|
4236
|
+
globalThis.fetch = (input, init = {}) => {
|
|
4237
|
+
const list = proxyUrlList();
|
|
4238
|
+
if (!list.length) return __origFetch(input, init);
|
|
4239
|
+
const p = list[Math.abs(__proxyIdx++) % list.length];
|
|
4240
|
+
return __origFetch(input, { ...init, dispatcher: proxyAgentFor(p) });
|
|
4241
|
+
};
|
|
4242
|
+
}
|
|
4243
|
+
} else if (__origFetch) {
|
|
4244
|
+
globalThis.fetch = __origFetch;
|
|
4245
|
+
__origFetch = null;
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
/* 2) python/cmd çocuk süreçleri (ddgs, requests... env'den okur) */
|
|
4249
|
+
if (enabled && httpUrls.length) {
|
|
4250
|
+
process.env.HTTP_PROXY = httpUrls[0];
|
|
4251
|
+
process.env.HTTPS_PROXY = httpUrls[0];
|
|
4252
|
+
process.env.ALL_PROXY = httpUrls[0];
|
|
4253
|
+
process.env.NO_PROXY = 'localhost,127.0.0.1';
|
|
4254
|
+
} else {
|
|
4255
|
+
delete process.env.HTTP_PROXY;
|
|
4256
|
+
delete process.env.HTTPS_PROXY;
|
|
4257
|
+
delete process.env.ALL_PROXY;
|
|
4258
|
+
delete process.env.NO_PROXY;
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4261
|
+
/* 3) dahili tarayıcı (Chromium; socks5 destekler) */
|
|
4262
|
+
try {
|
|
4263
|
+
const ses = session.fromPartition('persist:browser');
|
|
4264
|
+
if (ses) {
|
|
4265
|
+
if (enabled && allUrls.length) ses.setProxy({ proxyRules: allUrls[0] }).catch(() => {});
|
|
4266
|
+
else ses.setProxy({ mode: 'direct' }).catch(() => {});
|
|
4267
|
+
}
|
|
4268
|
+
} catch {}
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
ipcMain.handle('proxy:get', () => ({
|
|
4272
|
+
enabled: !!(settings.proxy && settings.proxy.enabled),
|
|
4273
|
+
urls: String((settings.proxy && settings.proxy.urls) || ''),
|
|
4274
|
+
}));
|
|
4275
|
+
|
|
4276
|
+
ipcMain.handle('proxy:set', (_e, cfg) => {
|
|
4277
|
+
settings.proxy = {
|
|
4278
|
+
enabled: !!(cfg && cfg.enabled),
|
|
4279
|
+
urls: String((cfg && cfg.urls) || '').slice(0, 2000),
|
|
4280
|
+
};
|
|
4281
|
+
saveSettings();
|
|
4282
|
+
applyProxy();
|
|
4283
|
+
return { ok: true, enabled: settings.proxy.enabled, urls: settings.proxy.urls };
|
|
4284
|
+
});
|
|
4285
|
+
|
|
4286
|
+
ipcMain.handle('proxy:test', async () => {
|
|
4287
|
+
const allUrls = proxyUrlList();
|
|
4288
|
+
if (!allUrls.length) return { ok: false, error: 'proxy adresi girilmemiş' };
|
|
4289
|
+
const u = allUrls[Math.abs(__proxyIdx++) % allUrls.length];
|
|
4290
|
+
const t0 = Date.now();
|
|
4291
|
+
try {
|
|
4292
|
+
const res = await (__origFetch || fetch)('https://api.ipify.org?format=json', {
|
|
4293
|
+
dispatcher: proxyAgentFor(u),
|
|
4294
|
+
signal: AbortSignal.timeout(15000),
|
|
4295
|
+
});
|
|
4296
|
+
const j = await res.json();
|
|
4297
|
+
return { ok: true, ip: j.ip, proxy: u, ms: Date.now() - t0 };
|
|
4298
|
+
} catch (e) {
|
|
4299
|
+
return { ok: false, error: String((e && e.message) || e).slice(0, 160), proxy: u };
|
|
4300
|
+
}
|
|
4301
|
+
});
|
|
4302
|
+
|
|
4303
|
+
/* ---------------- Cloudflare WARP (warp-plus) ----------------
|
|
4304
|
+
Açık kaynak warp-plus tek binary: yerel socks5 (127.0.0.1:8086) açar, o port
|
|
4305
|
+
Cloudflare WARP ağına tünel olur. Bedava, üyeliksiz (profil otomatik oluşur).
|
|
4306
|
+
Beast: indirme + başlatma + watchdog + kapatma. Sadece Beast trafiği geçer. */
|
|
4307
|
+
const WARP_PORT = 8086;
|
|
4308
|
+
const WARP_PROXY_LINE = 'socks5://127.0.0.1:' + WARP_PORT;
|
|
4309
|
+
let warpChild = null;
|
|
4310
|
+
let warpWatchdog = null;
|
|
4311
|
+
let warpStopping = false;
|
|
4312
|
+
|
|
4313
|
+
function warpBinPath() { return path.join(APP_DIR, 'bin', 'warp-plus.exe'); }
|
|
4314
|
+
function warpDir() { return path.join(APP_DIR, 'warp'); }
|
|
4315
|
+
function warpEnabled() { return !!(settings.warp && settings.warp.enabled); }
|
|
4316
|
+
|
|
4317
|
+
async function ensureWarpPlusBinary() {
|
|
4318
|
+
const bin = warpBinPath();
|
|
4319
|
+
if (fs.existsSync(bin)) return bin;
|
|
4320
|
+
fs.mkdirSync(path.dirname(bin), { recursive: true });
|
|
4321
|
+
log.info('main', 'warp-plus indiriliyor…');
|
|
4322
|
+
const rel = await (__origFetch || fetch)('https://api.github.com/repos/bepass-org/warp-plus/releases/latest', {
|
|
4323
|
+
headers: { 'User-Agent': 'beast-agent', Accept: 'application/vnd.github+json' },
|
|
4324
|
+
signal: AbortSignal.timeout(20000),
|
|
4325
|
+
});
|
|
4326
|
+
if (!rel.ok) throw new Error('sürüm bilgisi alınamadı (' + rel.status + ')');
|
|
4327
|
+
const j = await rel.json();
|
|
4328
|
+
const asset = (j.assets || []).find((a) => /warp-plus_windows-amd64\.zip$/i.test(a.name || ''));
|
|
4329
|
+
if (!asset) throw new Error('windows paketi bulunamadı');
|
|
4330
|
+
const zipPath = bin + '.zip';
|
|
4331
|
+
const res = await (__origFetch || fetch)(asset.browser_download_url, { signal: AbortSignal.timeout(300000) });
|
|
4332
|
+
if (!res.ok) throw new Error('indirme başarısız (' + res.status + ')');
|
|
4333
|
+
fs.writeFileSync(zipPath, Buffer.from(await res.arrayBuffer()));
|
|
4334
|
+
await new Promise((resolve, reject) => {
|
|
4335
|
+
spawn('powershell.exe', ['-NoProfile', '-Command', `Expand-Archive -Force -LiteralPath '${zipPath}' -DestinationPath '${path.dirname(bin)}'`])
|
|
4336
|
+
.on('exit', (c) => (c === 0 ? resolve() : reject(new Error('zip açılamadı'))))
|
|
4337
|
+
.on('error', reject);
|
|
4338
|
+
});
|
|
4339
|
+
try { fs.unlinkSync(zipPath); } catch {}
|
|
4340
|
+
if (!fs.existsSync(bin)) throw new Error('warp-plus.exe açılamadı');
|
|
4341
|
+
log.info('main', 'warp-plus indirildi: ' + bin);
|
|
4342
|
+
return bin;
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
function warpPortAlive() {
|
|
4346
|
+
const net = require('net');
|
|
4347
|
+
return new Promise((resolve) => {
|
|
4348
|
+
const s = net.connect({ host: '127.0.0.1', port: WARP_PORT, timeout: 1200 }, () => { s.destroy(); resolve(true); });
|
|
4349
|
+
s.on('error', () => resolve(false));
|
|
4350
|
+
s.on('timeout', () => { s.destroy(); resolve(false); });
|
|
4351
|
+
});
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
async function startWarp() {
|
|
4355
|
+
if (warpChild) return { ok: true };
|
|
4356
|
+
const bin = await ensureWarpPlusBinary();
|
|
4357
|
+
fs.mkdirSync(warpDir(), { recursive: true });
|
|
4358
|
+
try {
|
|
4359
|
+
warpChild = spawn(bin, [], { windowsHide: true, cwd: warpDir() });
|
|
4360
|
+
} catch (e) {
|
|
4361
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
4362
|
+
}
|
|
4363
|
+
warpChild.stdout.on('data', () => {});
|
|
4364
|
+
warpChild.stderr.on('data', () => {});
|
|
4365
|
+
warpChild.on('exit', () => {
|
|
4366
|
+
warpChild = null;
|
|
4367
|
+
if (warpEnabled() && !warpStopping) {
|
|
4368
|
+
/* watchdog: beklenmedik çıkışta 5 sn sonra yeniden başlat */
|
|
4369
|
+
clearTimeout(warpWatchdog);
|
|
4370
|
+
warpWatchdog = setTimeout(() => { startWarp().catch(() => {}); }, 5000);
|
|
4371
|
+
}
|
|
4372
|
+
});
|
|
4373
|
+
for (let i = 0; i < 180; i++) {
|
|
4374
|
+
if (await warpPortAlive()) { log.info('main', 'warp-plus hazır (127.0.0.1:' + WARP_PORT + ')'); return { ok: true }; }
|
|
4375
|
+
if (!warpChild) break;
|
|
4376
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
4377
|
+
}
|
|
4378
|
+
return { ok: false, error: 'warp-plus başlatılamadı (port açılmadı)' };
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
function stopWarp() {
|
|
4382
|
+
warpStopping = true;
|
|
4383
|
+
clearTimeout(warpWatchdog);
|
|
4384
|
+
try { if (warpChild) spawn('taskkill', ['/pid', String(warpChild.pid), '/T', '/F'], { windowsHide: true }); } catch {}
|
|
4385
|
+
try { if (warpChild) warpChild.kill(); } catch {}
|
|
4386
|
+
warpChild = null;
|
|
4387
|
+
setTimeout(() => { warpStopping = false; }, 1500);
|
|
4388
|
+
}
|
|
4389
|
+
|
|
4390
|
+
ipcMain.handle('warp:get', () => ({
|
|
4391
|
+
enabled: warpEnabled(),
|
|
4392
|
+
running: !!warpChild,
|
|
4393
|
+
installed: fs.existsSync(warpBinPath()),
|
|
4394
|
+
line: WARP_PROXY_LINE,
|
|
4395
|
+
}));
|
|
4396
|
+
|
|
4397
|
+
ipcMain.handle('warp:set', async (_e, on) => {
|
|
4398
|
+
const want = !!on;
|
|
4399
|
+
settings.warp = { enabled: want };
|
|
4400
|
+
saveSettings();
|
|
4401
|
+
if (want) {
|
|
4402
|
+
const r = await startWarp();
|
|
4403
|
+
if (!r.ok) {
|
|
4404
|
+
settings.warp = { enabled: false };
|
|
4405
|
+
saveSettings();
|
|
4406
|
+
return { ok: false, error: r.error || 'başlatılamadı' };
|
|
4407
|
+
}
|
|
4408
|
+
if (!settings.proxy) settings.proxy = { enabled: true, urls: '' };
|
|
4409
|
+
const lines = proxyUrlList();
|
|
4410
|
+
if (!lines.includes(WARP_PROXY_LINE)) lines.push(WARP_PROXY_LINE);
|
|
4411
|
+
settings.proxy.urls = lines.join('\n');
|
|
4412
|
+
settings.proxy.enabled = true; // WARP açıldıysa trafik ondan geçsin
|
|
4413
|
+
saveSettings();
|
|
4414
|
+
applyProxy();
|
|
4415
|
+
return { ok: true, enabled: true };
|
|
4416
|
+
}
|
|
4417
|
+
/* kapat: satırı listeden çıkar, başka proxy yoksa master'ı da kapat */
|
|
4418
|
+
const rest = proxyUrlList().filter((u) => u !== WARP_PROXY_LINE);
|
|
4419
|
+
settings.proxy.urls = rest.join('\n');
|
|
4420
|
+
if (!rest.length) settings.proxy.enabled = false;
|
|
4421
|
+
saveSettings();
|
|
4422
|
+
applyProxy();
|
|
4423
|
+
stopWarp();
|
|
4424
|
+
return { ok: true, enabled: false };
|
|
4425
|
+
});
|
|
4426
|
+
|
|
4162
4427
|
ipcMain.handle('settings:get', () => {
|
|
4163
4428
|
const out = JSON.parse(JSON.stringify(settings));
|
|
4164
4429
|
/* Sırların renderera düz metin gitmesini engelle */
|
package/src/preload.js
CHANGED
|
@@ -39,6 +39,11 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
39
39
|
eventsSubs: () => ipcRenderer.invoke('events:subs:list'),
|
|
40
40
|
eventUnsub: (id) => ipcRenderer.invoke('events:subs:remove', id),
|
|
41
41
|
getSettings: () => ipcRenderer.invoke('settings:get'),
|
|
42
|
+
proxyGet: () => ipcRenderer.invoke('proxy:get'),
|
|
43
|
+
proxySet: (cfg) => ipcRenderer.invoke('proxy:set', cfg),
|
|
44
|
+
proxyTest: () => ipcRenderer.invoke('proxy:test'),
|
|
45
|
+
warpGet: () => ipcRenderer.invoke('warp:get'),
|
|
46
|
+
warpSet: (on) => ipcRenderer.invoke('warp:set', on),
|
|
42
47
|
getFallout: () => ipcRenderer.invoke('fallout:get'),
|
|
43
48
|
setFallout: (cfg) => ipcRenderer.invoke('fallout:set', cfg),
|
|
44
49
|
getLimits: () => ipcRenderer.invoke('limits:get'),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -61,6 +61,33 @@
|
|
|
61
61
|
tab_websearch: 'Web Arama',
|
|
62
62
|
tab_limits: 'Limit Ayarları',
|
|
63
63
|
tab_security: 'Güvenlik',
|
|
64
|
+
tab_proxy: 'Proxy',
|
|
65
|
+
proxy_h2: 'Proxy',
|
|
66
|
+
proxy_sub: 'Tüm çıkış trafiğini (LLM API, dahili tarayıcı, web arama, http_fetch) proxy üzerinden geçir. Gizlilik, IP rotasyonu ve erişim engelleri için.',
|
|
67
|
+
proxy_enabled: 'Proxy aktif',
|
|
68
|
+
proxy_urls: 'Proxy listesi (çoklu satır = rotasyon)',
|
|
69
|
+
proxy_add: '+ Ekle',
|
|
70
|
+
proxy_host: 'IP / sunucu',
|
|
71
|
+
proxy_port: 'Port',
|
|
72
|
+
proxy_user: 'Kullanıcı (opsiyonel)',
|
|
73
|
+
proxy_pass: 'Şifre (opsiyonel)',
|
|
74
|
+
proxy_none: 'Henüz proxy eklenmedi — alanları doldurup + Ekle\u2019ye bas.',
|
|
75
|
+
proxy_invalid: 'IP ve Port zorunlu (port sayı olmalı)',
|
|
76
|
+
proxy_dup: 'Bu proxy zaten listede',
|
|
77
|
+
proxy_urls_ph: 'http://127.0.0.1:8080\nhttp://kullanici:sifre@sunucu:port\nsocks5://sunucu:1080 (socks5 yalnız dahili tarayıcı)',
|
|
78
|
+
proxy_save: 'Kaydet',
|
|
79
|
+
proxy_test: 'Test Et (çıkış IP)',
|
|
80
|
+
proxy_testing: 'Test ediliyor…',
|
|
81
|
+
proxy_test_ok: 'Bağlantı OK — çıkış IP:',
|
|
82
|
+
proxy_test_fail: 'Test başarısız:',
|
|
83
|
+
proxy_saved: 'Proxy ayarları kaydedildi',
|
|
84
|
+
proxy_note: 'Not: LLM API, http_fetch, TinyFish/Exa (http + socks5) ve dahili tarayıcı (http/socks5) destekler. Python motorları ve terminal süreçleri ortam değişkenleriyle yönlendirilir. Ayar anında uygulanır, yeniden başlatma gerekmez.',
|
|
85
|
+
warp_on: '☁ Cloudflare WARP Aç',
|
|
86
|
+
warp_off: '☁ Cloudflare WARP Kapat',
|
|
87
|
+
warp_state_on: 'çalışıyor ✓',
|
|
88
|
+
warp_state_starting: 'başlatılıyor…',
|
|
89
|
+
warp_state_off: 'kapalı',
|
|
90
|
+
warp_note: 'Bedava + üyeliksiz: Beast trafiğini Cloudflare ağından geçirir (yerel socks5). İlk açılışta ~10MB otomatik indirilir; kapatınca her şey normale döner.',
|
|
64
91
|
tab_update: 'Güncelleme',
|
|
65
92
|
up_h2: 'Güncelleme',
|
|
66
93
|
up_sub: 'Sürümler GitHub Releases\u2019ten gelir. Otomatik kontrol açılışta ve 6 saatte bir çalışır.',
|
|
@@ -579,6 +606,33 @@
|
|
|
579
606
|
tab_websearch: 'Web Search',
|
|
580
607
|
tab_limits: 'Limits',
|
|
581
608
|
tab_security: 'Security',
|
|
609
|
+
tab_proxy: 'Proxy',
|
|
610
|
+
proxy_h2: 'Proxy',
|
|
611
|
+
proxy_sub: 'Route all outgoing traffic (LLM APIs, built-in browser, web search, http_fetch) through a proxy. For privacy, IP rotation and access blocks.',
|
|
612
|
+
proxy_enabled: 'Proxy enabled',
|
|
613
|
+
proxy_urls: 'Proxy list (multiple lines = rotation)',
|
|
614
|
+
proxy_add: '+ Add',
|
|
615
|
+
proxy_host: 'IP / server',
|
|
616
|
+
proxy_port: 'Port',
|
|
617
|
+
proxy_user: 'Username (optional)',
|
|
618
|
+
proxy_pass: 'Password (optional)',
|
|
619
|
+
proxy_none: 'No proxy added yet — fill the fields and press + Add.',
|
|
620
|
+
proxy_invalid: 'IP and Port are required (port must be numeric)',
|
|
621
|
+
proxy_dup: 'This proxy is already in the list',
|
|
622
|
+
proxy_urls_ph: 'http://127.0.0.1:8080\nhttp://user:pass@server:port\nsocks5://server:1080 (socks5 built-in browser only)',
|
|
623
|
+
proxy_save: 'Save',
|
|
624
|
+
proxy_test: 'Test (exit IP)',
|
|
625
|
+
proxy_testing: 'Testing…',
|
|
626
|
+
proxy_test_ok: 'Connection OK — exit IP:',
|
|
627
|
+
proxy_test_fail: 'Test failed:',
|
|
628
|
+
proxy_saved: 'Proxy settings saved',
|
|
629
|
+
proxy_note: 'Note: LLM APIs, http_fetch, TinyFish/Exa (http + socks5) and the built-in browser (http/socks5) are supported. Python engines and terminal processes are routed via environment variables. Applied instantly — no restart needed.',
|
|
630
|
+
warp_on: '☁ Cloudflare WARP On',
|
|
631
|
+
warp_off: '☁ Cloudflare WARP Off',
|
|
632
|
+
warp_state_on: 'running ✓',
|
|
633
|
+
warp_state_starting: 'starting…',
|
|
634
|
+
warp_state_off: 'off',
|
|
635
|
+
warp_note: 'Free + no account: routes Beast traffic through the Cloudflare network (local socks5). First enable downloads ~10MB automatically; turning it off restores everything.',
|
|
582
636
|
tab_update: 'Update',
|
|
583
637
|
up_h2: 'Update',
|
|
584
638
|
up_sub: 'Versions come from GitHub Releases. Automatic check runs on startup and every 6 hours.',
|
package/src/renderer/index.html
CHANGED
|
@@ -164,6 +164,7 @@
|
|
|
164
164
|
<button class="tab" data-tab="dash" data-i18n="tab_dash">Dashboard</button>
|
|
165
165
|
<button class="tab" data-tab="limits" data-i18n="tab_limits">Limit</button>
|
|
166
166
|
<button class="tab" data-tab="sec" data-i18n="tab_security">Güvenlik</button>
|
|
167
|
+
<button class="tab" data-tab="proxy" data-i18n="tab_proxy">Proxy</button>
|
|
167
168
|
<button class="tab" data-tab="update" data-i18n="tab_update">Güncelleme</button>
|
|
168
169
|
</nav>
|
|
169
170
|
<div class="set-tabs-foot">
|
|
@@ -191,6 +192,7 @@
|
|
|
191
192
|
<div id="tab-dash" class="pane" hidden></div>
|
|
192
193
|
<div id="tab-limits" class="pane" hidden></div>
|
|
193
194
|
<div id="tab-sec" class="pane" hidden></div>
|
|
195
|
+
<div id="tab-proxy" class="pane" hidden></div>
|
|
194
196
|
<div id="tab-update" class="pane" hidden></div>
|
|
195
197
|
<div id="tab-cron" class="pane" hidden>
|
|
196
198
|
<h2 data-i18n="cron_h2">Cron Görevler</h2>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -635,7 +635,7 @@ function switchTab(name) {
|
|
|
635
635
|
document.querySelectorAll('#setTabs .tab').forEach((b) =>
|
|
636
636
|
b.classList.toggle('active', b.dataset.tab === name)
|
|
637
637
|
);
|
|
638
|
-
for (const p of ['provider', 'fallout', 'skills', 'agents', 'tts', 'email', 'integrations', 'websearch', 'events', 'cron', 'usage', 'logs', 'dash', 'limits', 'sec', 'update']) {
|
|
638
|
+
for (const p of ['provider', 'fallout', 'skills', 'agents', 'tts', 'email', 'integrations', 'websearch', 'events', 'cron', 'usage', 'logs', 'dash', 'limits', 'sec', 'proxy', 'update']) {
|
|
639
639
|
const el = $('#tab-' + p);
|
|
640
640
|
if (el) el.hidden = p !== name; // guard: eksik pane tüm sekmeleri kilitlemesin
|
|
641
641
|
}
|
|
@@ -646,6 +646,7 @@ function switchTab(name) {
|
|
|
646
646
|
if (name === 'dash') renderDashboardPane();
|
|
647
647
|
if (name === 'limits') renderLimitsPane();
|
|
648
648
|
if (name === 'sec') renderSecurityPane();
|
|
649
|
+
if (name === 'proxy') renderProxyPane();
|
|
649
650
|
if (name === 'update') renderUpdatePane(true);
|
|
650
651
|
if (name === 'agents') refreshAgentsPane();
|
|
651
652
|
if (name === 'websearch') renderWebSearchPane();
|
|
@@ -1790,8 +1791,134 @@ async function renderSecurityPane() {
|
|
|
1790
1791
|
}
|
|
1791
1792
|
}
|
|
1792
1793
|
|
|
1793
|
-
/* ----------------
|
|
1794
|
+
/* ---------------- Proxy sekmesi ----------------
|
|
1795
|
+
Kullanıcı yalnız IP/port/kullanıcı/şifre alanlarını doldurur; URL'i biz kurarız.
|
|
1796
|
+
Eklenen her satır bir proxy — çoklu satır = Node tarafında round-robin rotasyon. */
|
|
1797
|
+
let proxyDraft = null; // { enabled, items:[line,...] } — sekme açıkken çalışma kopyası
|
|
1798
|
+
|
|
1799
|
+
function parseProxyLine(line) {
|
|
1800
|
+
const m = String(line || '').trim().match(/^(https?|socks5):\/\/(?:([^:@/]+):([^@/]*)@)?([^:/]+):(\d+)$/i);
|
|
1801
|
+
if (!m) return null;
|
|
1802
|
+
return { proto: m[1].toLowerCase(), user: decodeURIComponent(m[2] || ''), pass: decodeURIComponent(m[3] || ''), host: m[4], port: m[5] };
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
function proxyLineDisplay(line) {
|
|
1806
|
+
const p = parseProxyLine(line);
|
|
1807
|
+
if (!p) return escapeHtml(line);
|
|
1808
|
+
const creds = p.user ? p.user + ':' + (p.pass ? '•••' : '') + '@' : '';
|
|
1809
|
+
return `${p.proto}://${escapeHtml(creds)}${escapeHtml(p.host)}:${p.port}`;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
async function renderProxyPane() {
|
|
1813
|
+
const pane = $('#tab-proxy');
|
|
1814
|
+
if (!pane) return;
|
|
1815
|
+
if (!proxyDraft) {
|
|
1816
|
+
const [cfg, ws] = await Promise.all([
|
|
1817
|
+
beast.proxyGet().catch(() => ({ enabled: false, urls: '' })),
|
|
1818
|
+
beast.warpGet().catch(() => ({ enabled: false, running: false, installed: false })),
|
|
1819
|
+
]);
|
|
1820
|
+
proxyDraft = {
|
|
1821
|
+
enabled: !!cfg.enabled,
|
|
1822
|
+
items: String(cfg.urls || '').split(/[\n,;]+/).map((s) => s.trim()).filter(Boolean),
|
|
1823
|
+
warp: ws || {},
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
const warp = proxyDraft.warp || {};
|
|
1827
|
+
const warpState = warp.enabled ? (warp.running ? _t('warp_state_on') : _t('warp_state_starting')) : _t('warp_state_off');
|
|
1828
|
+
|
|
1829
|
+
const listHtml = proxyDraft.items.length
|
|
1830
|
+
? proxyDraft.items.map((line, i) =>
|
|
1831
|
+
`<div class="px-row"><code>${proxyLineDisplay(line)}</code><button class="px-del" data-i="${i}" title="${_t('proxy_remove')}">✕</button></div>`
|
|
1832
|
+
).join('')
|
|
1833
|
+
: `<div class="gh-empty" style="padding:10px">${_t('proxy_none')}</div>`;
|
|
1834
|
+
|
|
1835
|
+
pane.innerHTML =
|
|
1836
|
+
'<h2>' + _t('proxy_h2') + '</h2>' +
|
|
1837
|
+
'<div class="sub">' + _t('proxy_sub') + '</div>' +
|
|
1838
|
+
'<div class="warp-card">' +
|
|
1839
|
+
`<div class="warp-head"><span>☁ Cloudflare WARP</span><span class="warp-state">${escapeHtml(warpState)}</span></div>` +
|
|
1840
|
+
`<button id="pxWarp" class="btn ${warp.enabled ? 'ghost' : ''}">${warp.enabled ? _t('warp_off') : _t('warp_on')}</button>` +
|
|
1841
|
+
`<div class="sub" style="margin-top:6px">${_t('warp_note')}</div>` +
|
|
1842
|
+
'</div>' +
|
|
1843
|
+
'<div class="fo-toggles" style="margin-top:12px">' +
|
|
1844
|
+
`<label class="lock-row"><input type="checkbox" id="pxOn" ${proxyDraft.enabled ? 'checked' : ''}/><span>${_t('proxy_enabled')}</span></label>` +
|
|
1845
|
+
'</div>' +
|
|
1846
|
+
`<label class="mem-label">${_t('proxy_urls')}</label>` +
|
|
1847
|
+
`<div id="pxList">${listHtml}</div>` +
|
|
1848
|
+
'<div class="px-add-row">' +
|
|
1849
|
+
`<select id="pxProto"><option value="http">http</option><option value="socks5">socks5</option></select>` +
|
|
1850
|
+
`<input id="pxHost" placeholder="${_t('proxy_host')}" spellcheck="false"/>` +
|
|
1851
|
+
`<input id="pxPort" placeholder="${_t('proxy_port')}" inputmode="numeric"/>` +
|
|
1852
|
+
`<input id="pxUser" placeholder="${_t('proxy_user')}" autocomplete="off" spellcheck="false"/>` +
|
|
1853
|
+
`<input id="pxPass" placeholder="${_t('proxy_pass')}" type="password" autocomplete="off"/>` +
|
|
1854
|
+
`<button id="pxAdd" class="btn" style="margin-top:0">${_t('proxy_add')}</button>` +
|
|
1855
|
+
'</div>' +
|
|
1856
|
+
'<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:10px">' +
|
|
1857
|
+
`<button id="pxSave" class="btn">${_t('proxy_save')}</button>` +
|
|
1858
|
+
`<button id="pxTest" class="btn ghost">${_t('proxy_test')}</button>` +
|
|
1859
|
+
'</div>' +
|
|
1860
|
+
`<div id="pxTestOut" class="sub" style="margin-top:8px"></div>` +
|
|
1861
|
+
'<div class="sub" style="margin-top:10px">' + _t('proxy_note') + '</div>';
|
|
1862
|
+
|
|
1863
|
+
pane.querySelector('#pxOn').addEventListener('change', (e) => { proxyDraft.enabled = e.target.checked; });
|
|
1794
1864
|
|
|
1865
|
+
pane.querySelector('#pxWarp').addEventListener('click', async () => {
|
|
1866
|
+
const btn = pane.querySelector('#pxWarp');
|
|
1867
|
+
const st = pane.querySelector('#pxWarpState');
|
|
1868
|
+
const want = !warp.enabled;
|
|
1869
|
+
btn.disabled = true;
|
|
1870
|
+
st.textContent = want ? '⏳ ' + _t('warp_state_starting') : '⏳ ' + _t('warp_state_off');
|
|
1871
|
+
const r = await beast.warpSet(want).catch((e) => ({ ok: false, error: String(e) }));
|
|
1872
|
+
if (!(r && r.ok)) {
|
|
1873
|
+
btn.disabled = false;
|
|
1874
|
+
st.textContent = '⚠ ' + ((r && r.error) || 'hata');
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
proxyDraft = null; // listeyi (socks satırı eklenmiş/çıkarılmış) tazele
|
|
1878
|
+
renderProxyPane();
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1881
|
+
const collect = () => {
|
|
1882
|
+
const host = pane.querySelector('#pxHost').value.trim();
|
|
1883
|
+
const port = pane.querySelector('#pxPort').value.trim();
|
|
1884
|
+
const user = pane.querySelector('#pxUser').value.trim();
|
|
1885
|
+
const pass = pane.querySelector('#pxPass').value;
|
|
1886
|
+
const proto = pane.querySelector('#pxProto').value || 'http';
|
|
1887
|
+
if (!host || !/^\d{1,5}$/.test(port)) { toast(_t('proxy_invalid')); return null; }
|
|
1888
|
+
const creds = user ? encodeURIComponent(user) + (pass ? ':' + encodeURIComponent(pass) : '') + '@' : '';
|
|
1889
|
+
return `${proto}://${creds}${host}:${port}`;
|
|
1890
|
+
};
|
|
1891
|
+
pane.querySelector('#pxAdd').addEventListener('click', () => {
|
|
1892
|
+
const line = collect();
|
|
1893
|
+
if (!line) return;
|
|
1894
|
+
if (proxyDraft.items.includes(line)) { toast(_t('proxy_dup')); return; }
|
|
1895
|
+
proxyDraft.items.push(line);
|
|
1896
|
+
renderProxyPane();
|
|
1897
|
+
});
|
|
1898
|
+
pane.querySelectorAll('.px-del').forEach((b) =>
|
|
1899
|
+
b.addEventListener('click', () => {
|
|
1900
|
+
proxyDraft.items.splice(Number(b.dataset.i), 1);
|
|
1901
|
+
renderProxyPane();
|
|
1902
|
+
})
|
|
1903
|
+
);
|
|
1904
|
+
|
|
1905
|
+
pane.querySelector('#pxSave').addEventListener('click', async () => {
|
|
1906
|
+
const r = await beast.proxySet({ enabled: proxyDraft.enabled, urls: proxyDraft.items.join('\n') }).catch(() => null);
|
|
1907
|
+
if (r && r.ok) toast(_t('proxy_saved'));
|
|
1908
|
+
else toast((r && r.error) || 'hata');
|
|
1909
|
+
});
|
|
1910
|
+
pane.querySelector('#pxTest').addEventListener('click', async () => {
|
|
1911
|
+
const out = pane.querySelector('#pxTestOut');
|
|
1912
|
+
out.textContent = '⏳ ' + _t('proxy_testing');
|
|
1913
|
+
/* önce kaydet — test, kayıtlı ayarla çalışır */
|
|
1914
|
+
await beast.proxySet({ enabled: proxyDraft.enabled, urls: proxyDraft.items.join('\n') }).catch(() => null);
|
|
1915
|
+
const r = await beast.proxyTest().catch((e) => ({ ok: false, error: String(e) }));
|
|
1916
|
+
if (r && r.ok) out.innerHTML = `✅ ${_t('proxy_test_ok')} <b>${escapeHtml(r.ip || '?')}</b> · ${escapeHtml(r.proxy || '')} · ${r.ms || '?'}ms`;
|
|
1917
|
+
else out.innerHTML = `⚠ ${_t('proxy_test_fail')} ${escapeHtml((r && r.error) || '?')}`;
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
/* ---------------- Update: sürüm kontrol + otomatik güncelleme ---------------- */
|
|
1795
1922
|
let updatePaneTimer = null;
|
|
1796
1923
|
|
|
1797
1924
|
function renderUpdateStateHtml(st) {
|
package/src/renderer/style.css
CHANGED
|
@@ -2852,3 +2852,62 @@ body.browser-open #ghOverlay { right: var(--bw, 480px); }
|
|
|
2852
2852
|
flex: none;
|
|
2853
2853
|
text-align: center;
|
|
2854
2854
|
}
|
|
2855
|
+
|
|
2856
|
+
/* proxy ekleme satırı + liste */
|
|
2857
|
+
.px-add-row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 6px; }
|
|
2858
|
+
.px-add-row select,
|
|
2859
|
+
.px-add-row input {
|
|
2860
|
+
background: var(--panel);
|
|
2861
|
+
border: 1px solid var(--border);
|
|
2862
|
+
border-radius: 7px;
|
|
2863
|
+
color: var(--text);
|
|
2864
|
+
font-size: 12.5px;
|
|
2865
|
+
padding: 6px 9px;
|
|
2866
|
+
outline: none;
|
|
2867
|
+
}
|
|
2868
|
+
.px-add-row select { flex: 0 0 auto; }
|
|
2869
|
+
.px-add-row input:focus { border-color: var(--accent); }
|
|
2870
|
+
#pxHost { flex: 2 1 140px; }
|
|
2871
|
+
#pxPort { flex: 0 1 80px; }
|
|
2872
|
+
#pxUser, #pxPass { flex: 1 1 110px; }
|
|
2873
|
+
.px-row {
|
|
2874
|
+
display: flex;
|
|
2875
|
+
align-items: center;
|
|
2876
|
+
gap: 8px;
|
|
2877
|
+
border: 1px solid var(--border);
|
|
2878
|
+
background: var(--panel);
|
|
2879
|
+
border-radius: 8px;
|
|
2880
|
+
padding: 6px 10px;
|
|
2881
|
+
margin-bottom: 6px;
|
|
2882
|
+
}
|
|
2883
|
+
.px-row code {
|
|
2884
|
+
font-family: var(--mono, monospace);
|
|
2885
|
+
font-size: 12px;
|
|
2886
|
+
color: var(--text);
|
|
2887
|
+
overflow: hidden;
|
|
2888
|
+
text-overflow: ellipsis;
|
|
2889
|
+
white-space: nowrap;
|
|
2890
|
+
flex: 1;
|
|
2891
|
+
}
|
|
2892
|
+
.px-row .px-del {
|
|
2893
|
+
background: none;
|
|
2894
|
+
border: none;
|
|
2895
|
+
color: var(--muted);
|
|
2896
|
+
cursor: pointer;
|
|
2897
|
+
font-size: 13px;
|
|
2898
|
+
padding: 2px 6px;
|
|
2899
|
+
border-radius: 6px;
|
|
2900
|
+
flex: none;
|
|
2901
|
+
}
|
|
2902
|
+
.px-row .px-del:hover { color: var(--err); background: var(--panel2); }
|
|
2903
|
+
|
|
2904
|
+
/* WARP kartı */
|
|
2905
|
+
.warp-card {
|
|
2906
|
+
border: 1px solid var(--border);
|
|
2907
|
+
background: var(--panel);
|
|
2908
|
+
border-radius: 10px;
|
|
2909
|
+
padding: 12px 14px;
|
|
2910
|
+
margin-top: 12px;
|
|
2911
|
+
}
|
|
2912
|
+
.warp-head { display: flex; justify-content: space-between; align-items: center; font-weight: 800; margin-bottom: 8px; }
|
|
2913
|
+
.warp-state { color: var(--muted); font-weight: 400; font-size: 12px; }
|