@xulongzhe/clawbench-desktop-linux-x64-payload 0.102.0 → 0.104.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 +1 -1
- package/payload.json +1 -1
- package/resources/app.asar +0 -0
- package/resources/login.html +206 -2
- package/resources/url-utils.js +48 -0
package/package.json
CHANGED
package/payload.json
CHANGED
package/resources/app.asar
CHANGED
|
Binary file
|
package/resources/login.html
CHANGED
|
@@ -29,7 +29,14 @@
|
|
|
29
29
|
connection_failed: 'Connection failed: ',
|
|
30
30
|
connect_error: 'Unable to connect. Check address and network.',
|
|
31
31
|
wrong_password: 'Wrong password',
|
|
32
|
-
too_many_attempts: 'Too many attempts'
|
|
32
|
+
too_many_attempts: 'Too many attempts',
|
|
33
|
+
// Splash overlay (?splash=1). Wording mirrors Android's splash_status_*
|
|
34
|
+
// strings so the two platforms read identically.
|
|
35
|
+
splash_connecting: 'Connecting…',
|
|
36
|
+
splash_loading: 'Loading page…',
|
|
37
|
+
splash_rendering: 'Rendering…',
|
|
38
|
+
splash_initializing: 'Initializing app…',
|
|
39
|
+
splash_cancel: 'Cancel connection'
|
|
33
40
|
},
|
|
34
41
|
zh: {
|
|
35
42
|
slogan: '从终端到桌面',
|
|
@@ -53,7 +60,14 @@
|
|
|
53
60
|
connection_failed: '连接失败: ',
|
|
54
61
|
connect_error: '无法连接到服务器,请检查地址和网络。',
|
|
55
62
|
wrong_password: '密码错误',
|
|
56
|
-
too_many_attempts: '尝试次数过多'
|
|
63
|
+
too_many_attempts: '尝试次数过多',
|
|
64
|
+
// Splash overlay (?splash=1). Wording mirrors Android's splash_status_*
|
|
65
|
+
// strings so the two platforms read identically.
|
|
66
|
+
splash_connecting: '正在连接…',
|
|
67
|
+
splash_loading: '正在加载页面…',
|
|
68
|
+
splash_rendering: '正在渲染…',
|
|
69
|
+
splash_initializing: '正在初始化应用…',
|
|
70
|
+
splash_cancel: '取消连接'
|
|
57
71
|
}
|
|
58
72
|
};
|
|
59
73
|
var LANG = 'en';
|
|
@@ -1272,9 +1286,122 @@
|
|
|
1272
1286
|
padding-bottom: env(safe-area-inset-bottom);
|
|
1273
1287
|
}
|
|
1274
1288
|
}
|
|
1289
|
+
/* ── Splash overlay (?splash=1) ──────────────────────────────────────────
|
|
1290
|
+
Shown by the main process while connecting to a server and booting the
|
|
1291
|
+
app. Mirrors Android's native splash: a circular logo with a rotating
|
|
1292
|
+
sweep ring, a stage line, and a cancel button. The ring reproduces
|
|
1293
|
+
android/app/src/main/res/drawable/splash_sweep.xml (a dim full ring plus a
|
|
1294
|
+
~90° bright arc spun linearly over 1.6s). */
|
|
1295
|
+
body.splash-mode .content { display: none; }
|
|
1296
|
+
|
|
1297
|
+
.splash {
|
|
1298
|
+
position: fixed;
|
|
1299
|
+
inset: 0;
|
|
1300
|
+
display: none;
|
|
1301
|
+
flex-direction: column;
|
|
1302
|
+
align-items: center;
|
|
1303
|
+
justify-content: center;
|
|
1304
|
+
background: var(--bg-primary);
|
|
1305
|
+
z-index: 100;
|
|
1306
|
+
}
|
|
1307
|
+
body.splash-mode .splash { display: flex; }
|
|
1308
|
+
|
|
1309
|
+
/* Fade-out hook, called by the main process just before it hides the view. */
|
|
1310
|
+
.splash.is-fading { opacity: 0; transition: opacity 200ms ease; }
|
|
1311
|
+
|
|
1312
|
+
.splash-logo-wrapper {
|
|
1313
|
+
position: relative;
|
|
1314
|
+
width: 132px;
|
|
1315
|
+
height: 132px;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/* Sweep ring: a dim circle with one bright arc, spun by rotating the box. */
|
|
1319
|
+
.splash-sweep {
|
|
1320
|
+
position: absolute;
|
|
1321
|
+
inset: 0;
|
|
1322
|
+
border-radius: 50%;
|
|
1323
|
+
border: 2px solid var(--border-color);
|
|
1324
|
+
opacity: 0.4;
|
|
1325
|
+
animation: splash-spin 1.6s linear infinite;
|
|
1326
|
+
}
|
|
1327
|
+
.splash-sweep::after {
|
|
1328
|
+
content: '';
|
|
1329
|
+
position: absolute;
|
|
1330
|
+
inset: -2px;
|
|
1331
|
+
border-radius: 50%;
|
|
1332
|
+
border: 3px solid transparent;
|
|
1333
|
+
border-top-color: var(--accent-color);
|
|
1334
|
+
border-right-color: var(--accent-color);
|
|
1335
|
+
/* Two adjacent edges approximate the native 90° sweep segment without an
|
|
1336
|
+
SVG, so no extra asset ships for this. */
|
|
1337
|
+
transform: rotate(45deg);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
@keyframes splash-spin {
|
|
1341
|
+
from { transform: rotate(0deg); }
|
|
1342
|
+
to { transform: rotate(360deg); }
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
.splash-logo {
|
|
1346
|
+
position: absolute;
|
|
1347
|
+
top: 12px;
|
|
1348
|
+
left: 12px;
|
|
1349
|
+
width: 108px;
|
|
1350
|
+
height: 108px;
|
|
1351
|
+
border-radius: 50%;
|
|
1352
|
+
object-fit: cover;
|
|
1353
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
.splash-title {
|
|
1357
|
+
margin-top: 24px;
|
|
1358
|
+
font-size: 20px;
|
|
1359
|
+
font-weight: 500;
|
|
1360
|
+
color: var(--text-primary);
|
|
1361
|
+
letter-spacing: 0.06em;
|
|
1362
|
+
opacity: 0.85;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
.splash-stage {
|
|
1366
|
+
margin-top: 12px;
|
|
1367
|
+
font-size: 14px;
|
|
1368
|
+
font-weight: 500;
|
|
1369
|
+
color: var(--text-muted);
|
|
1370
|
+
opacity: 0.7;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.splash-cancel {
|
|
1374
|
+
margin-top: 28px;
|
|
1375
|
+
padding: 8px 16px;
|
|
1376
|
+
font-size: 13px;
|
|
1377
|
+
font-family: inherit;
|
|
1378
|
+
color: var(--text-hint);
|
|
1379
|
+
background: transparent;
|
|
1380
|
+
border: 1px solid var(--border-color);
|
|
1381
|
+
border-radius: 6px;
|
|
1382
|
+
cursor: pointer;
|
|
1383
|
+
transition: background 150ms ease, color 150ms ease;
|
|
1384
|
+
}
|
|
1385
|
+
.splash-cancel:hover {
|
|
1386
|
+
background: var(--bg-tertiary);
|
|
1387
|
+
color: var(--text-primary);
|
|
1388
|
+
}
|
|
1275
1389
|
</style>
|
|
1276
1390
|
</head>
|
|
1277
1391
|
<body>
|
|
1392
|
+
<!-- Splash overlay (?splash=1). Rendered by the main process as a native
|
|
1393
|
+
WebContentsView floating above the app while it connects and boots; see
|
|
1394
|
+
desktop/src/main/splash.ts. Hidden unless the query flag is present. -->
|
|
1395
|
+
<div class="splash" id="splash">
|
|
1396
|
+
<div class="splash-logo-wrapper">
|
|
1397
|
+
<div class="splash-sweep"></div>
|
|
1398
|
+
<img class="splash-logo" src="logo.png" alt="ClawBench">
|
|
1399
|
+
</div>
|
|
1400
|
+
<div class="splash-title">ClawBench</div>
|
|
1401
|
+
<div class="splash-stage" id="splashStage" data-i18n="splash_connecting">正在连接…</div>
|
|
1402
|
+
<button type="button" class="splash-cancel" id="splashCancel" data-i18n="splash_cancel">取消连接</button>
|
|
1403
|
+
</div>
|
|
1404
|
+
|
|
1278
1405
|
<div class="bg-gradient"></div>
|
|
1279
1406
|
<div class="bg-grid"></div>
|
|
1280
1407
|
|
|
@@ -1402,6 +1529,7 @@
|
|
|
1402
1529
|
</div>
|
|
1403
1530
|
</div>
|
|
1404
1531
|
|
|
1532
|
+
<script src="url-utils.js"></script>
|
|
1405
1533
|
<script>
|
|
1406
1534
|
var connecting = false;
|
|
1407
1535
|
var savedServers = [];
|
|
@@ -1419,10 +1547,64 @@
|
|
|
1419
1547
|
});
|
|
1420
1548
|
}
|
|
1421
1549
|
|
|
1550
|
+
// ── Splash overlay (?splash=1) ──────────────────────────────────────────
|
|
1551
|
+
// The main process loads this same page with ?splash=1 as a native view
|
|
1552
|
+
// floating over the app while it connects and boots. In that mode the login
|
|
1553
|
+
// form is irrelevant: only the splash markup is shown, and the process drives
|
|
1554
|
+
// the stage text through __splashSetStage.
|
|
1555
|
+
var SPLASH_MODE = /(?:^|[?&])splash=1(?:&|$)/.test(window.location.search);
|
|
1556
|
+
var SPLASH_STAGE_KEYS = {
|
|
1557
|
+
connecting: 'splash_connecting',
|
|
1558
|
+
loading: 'splash_loading',
|
|
1559
|
+
rendering: 'splash_rendering',
|
|
1560
|
+
initializing: 'splash_initializing'
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
/** Advance the splash stage text. Called from the main process. */
|
|
1564
|
+
window.__splashSetStage = function(stage) {
|
|
1565
|
+
var key = SPLASH_STAGE_KEYS[stage];
|
|
1566
|
+
if (!key) return;
|
|
1567
|
+
var el = document.getElementById('splashStage');
|
|
1568
|
+
if (el) el.textContent = t(key);
|
|
1569
|
+
};
|
|
1570
|
+
|
|
1571
|
+
/** Begin the splash fade-out. The main process hides the view shortly after. */
|
|
1572
|
+
window.__splashFadeOut = function() {
|
|
1573
|
+
var el = document.getElementById('splash');
|
|
1574
|
+
if (el) el.classList.add('is-fading');
|
|
1575
|
+
};
|
|
1576
|
+
|
|
1577
|
+
/**
|
|
1578
|
+
* Reset the overlay for a fresh show. The page is REUSED across connects
|
|
1579
|
+
* (the main process only loads it once), so the fade class left by the
|
|
1580
|
+
* previous dismissal must be cleared — otherwise the second connect renders
|
|
1581
|
+
* the overlay fully transparent.
|
|
1582
|
+
*/
|
|
1583
|
+
window.__splashReset = function() {
|
|
1584
|
+
var el = document.getElementById('splash');
|
|
1585
|
+
if (el) el.classList.remove('is-fading');
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
if (SPLASH_MODE) {
|
|
1589
|
+
document.body.classList.add('splash-mode');
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1422
1592
|
window.addEventListener('DOMContentLoaded', function() {
|
|
1423
1593
|
// Apply i18n to all elements with data-i18n / data-i18n-placeholder
|
|
1424
1594
|
applyI18n();
|
|
1425
1595
|
|
|
1596
|
+
if (SPLASH_MODE) {
|
|
1597
|
+
// Splash mode is display-only apart from the cancel button; skip the
|
|
1598
|
+
// login form's data loading entirely.
|
|
1599
|
+
var cancelBtn = document.getElementById('splashCancel');
|
|
1600
|
+
if (cancelBtn) {
|
|
1601
|
+
cancelBtn.addEventListener('click', function() {
|
|
1602
|
+
try { ClawBenchNative.cancelSplash(); } catch(e) {}
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1426
1608
|
// Load server list (single source of truth)
|
|
1427
1609
|
loadServerList();
|
|
1428
1610
|
|
|
@@ -1511,6 +1693,28 @@
|
|
|
1511
1693
|
}
|
|
1512
1694
|
});
|
|
1513
1695
|
|
|
1696
|
+
// Event: blur on the host field -> normalize a full URL into protocol + port.
|
|
1697
|
+
// Blur, not paste/input: the field is only rewritten after the user leaves it,
|
|
1698
|
+
// so typing is never disturbed mid-keystroke. Reading .value (rather than
|
|
1699
|
+
// clipboard data) also sidesteps WebView clipboard restrictions.
|
|
1700
|
+
document.getElementById('addHost').addEventListener('blur', function() {
|
|
1701
|
+
var parsed = parseServerInput(document.getElementById('addHost').value);
|
|
1702
|
+
// Not a URL we understand -> leave the field exactly as the user typed it.
|
|
1703
|
+
if (!parsed) return;
|
|
1704
|
+
|
|
1705
|
+
hideError('addErrorMsg');
|
|
1706
|
+
document.getElementById('addHost').value = parsed.host;
|
|
1707
|
+
// Only override the port when the URL carried one explicitly; the field
|
|
1708
|
+
// keeps its default (20000) otherwise.
|
|
1709
|
+
if (parsed.port !== null) {
|
|
1710
|
+
document.getElementById('addPort').value = parsed.port;
|
|
1711
|
+
}
|
|
1712
|
+
// No scheme in the input -> leave the radio untouched.
|
|
1713
|
+
if (parsed.protocol !== null) {
|
|
1714
|
+
document.querySelector('input[name="addProtocol"][value="' + parsed.protocol + '"]').checked = true;
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1514
1718
|
// Event: show add form
|
|
1515
1719
|
document.getElementById('addServerBtn').addEventListener('click', function() {
|
|
1516
1720
|
showAddForm();
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a server address into protocol / host / port.
|
|
3
|
+
*
|
|
4
|
+
* Pure and dependency-free on purpose: it must be evaluable in a bare sandbox
|
|
5
|
+
* (no DOM, no ClawBenchNative). Deliberately NOT built on `new URL()`:
|
|
6
|
+
* new URL('192.168.1.100:8080') -> throws Invalid URL
|
|
7
|
+
* new URL('example.com:8080') -> protocol "example.com:", hostname ""
|
|
8
|
+
* so the scheme-less form users actually type cannot go through it. A single
|
|
9
|
+
* regex treats both "with scheme" and "without scheme" uniformly.
|
|
10
|
+
*
|
|
11
|
+
* Returns { protocol: 'http'|'https'|null, host: string, port: string|null },
|
|
12
|
+
* or null when the input is not a server address we understand. Callers must
|
|
13
|
+
* treat null as "leave the field alone".
|
|
14
|
+
*/
|
|
15
|
+
function parseServerInput(text) {
|
|
16
|
+
if (typeof text !== 'string') return null;
|
|
17
|
+
var s = text.trim();
|
|
18
|
+
if (!s) return null;
|
|
19
|
+
|
|
20
|
+
var protocol = null;
|
|
21
|
+
var rest = s;
|
|
22
|
+
|
|
23
|
+
// Optional scheme. Anything other than http/https is not ours to interpret.
|
|
24
|
+
var schemeMatch = rest.match(/^([A-Za-z][A-Za-z0-9+.-]*):\/\//);
|
|
25
|
+
if (schemeMatch) {
|
|
26
|
+
var scheme = schemeMatch[1].toLowerCase();
|
|
27
|
+
if (scheme !== 'http' && scheme !== 'https') return null;
|
|
28
|
+
protocol = scheme;
|
|
29
|
+
rest = rest.slice(schemeMatch[0].length);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Drop path, query and fragment — only scheme/host/port are meaningful here.
|
|
33
|
+
rest = rest.split(/[/?#]/)[0];
|
|
34
|
+
|
|
35
|
+
var host = rest;
|
|
36
|
+
var port = null;
|
|
37
|
+
var portMatch = rest.match(/^(.*):(\d+)$/);
|
|
38
|
+
if (portMatch) {
|
|
39
|
+
host = portMatch[1];
|
|
40
|
+
port = portMatch[2];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Host must look like a hostname or IPv4 literal. This is also what rejects
|
|
44
|
+
// 'not a url', 'http://' (empty host) and 'user:pass@host' (contains '@').
|
|
45
|
+
if (!/^[A-Za-z0-9._-]+$/.test(host)) return null;
|
|
46
|
+
|
|
47
|
+
return { protocol: protocol, host: host, port: port };
|
|
48
|
+
}
|