@chrysb/alphaclaw 0.2.1 → 0.2.3
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/lib/public/css/shell.css +96 -1
- package/lib/public/css/theme.css +26 -0
- package/lib/public/js/app.js +70 -22
- package/lib/public/js/components/confirm-dialog.js +66 -0
- package/lib/public/js/components/credentials-modal.js +1 -1
- package/lib/public/js/components/gateway.js +141 -36
- package/lib/public/js/components/google.js +15 -7
- package/lib/public/js/components/update-action-button.js +66 -0
- package/lib/public/login.html +1 -0
- package/lib/public/setup.html +1 -0
- package/package.json +2 -2
package/lib/public/css/shell.css
CHANGED
|
@@ -175,9 +175,104 @@
|
|
|
175
175
|
|
|
176
176
|
.statusbar-left, .statusbar-right { display: flex; align-items: center; gap: 16px; margin-left: 2px; }
|
|
177
177
|
|
|
178
|
+
.mobile-topbar {
|
|
179
|
+
display: none;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.mobile-topbar-menu {
|
|
183
|
+
display: inline-flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
justify-content: center;
|
|
186
|
+
width: 32px;
|
|
187
|
+
height: 32px;
|
|
188
|
+
border: 1px solid var(--border-strong);
|
|
189
|
+
border-radius: 8px;
|
|
190
|
+
background: var(--bg-hover);
|
|
191
|
+
color: var(--text);
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
}
|
|
194
|
+
.mobile-topbar-menu:hover { background: var(--bg-active); }
|
|
195
|
+
|
|
196
|
+
.mobile-topbar-title {
|
|
197
|
+
display: inline-flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
justify-content: center;
|
|
200
|
+
width: 100%;
|
|
201
|
+
text-align: center;
|
|
202
|
+
font-size: 14px;
|
|
203
|
+
letter-spacing: 0.03em;
|
|
204
|
+
color: var(--text-muted);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.mobile-sidebar-overlay {
|
|
208
|
+
display: none;
|
|
209
|
+
}
|
|
210
|
+
|
|
178
211
|
/* ── Responsive ────────────────────────────────── */
|
|
179
212
|
|
|
180
213
|
@media (max-width: 768px) {
|
|
181
214
|
.app-shell { grid-template-columns: 1fr; }
|
|
182
|
-
.app-
|
|
215
|
+
.app-content {
|
|
216
|
+
padding: 0 14px 12px;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.mobile-topbar {
|
|
220
|
+
display: flex;
|
|
221
|
+
align-items: center;
|
|
222
|
+
justify-content: center;
|
|
223
|
+
position: sticky;
|
|
224
|
+
top: 0;
|
|
225
|
+
z-index: 15;
|
|
226
|
+
background: var(--panel-bg-contrast);
|
|
227
|
+
border: 0;
|
|
228
|
+
border-bottom: 1px solid var(--panel-border-contrast);
|
|
229
|
+
border-radius: 0;
|
|
230
|
+
min-height: 52px;
|
|
231
|
+
padding: 8px 14px;
|
|
232
|
+
margin: 0 -14px 10px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.mobile-topbar.is-scrolled {
|
|
236
|
+
background: var(--bg-content);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.mobile-topbar-menu {
|
|
240
|
+
position: absolute;
|
|
241
|
+
left: 14px;
|
|
242
|
+
top: 50%;
|
|
243
|
+
transform: translateY(-50%);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.app-sidebar {
|
|
247
|
+
display: flex;
|
|
248
|
+
position: fixed;
|
|
249
|
+
top: 0;
|
|
250
|
+
left: 0;
|
|
251
|
+
bottom: 24px;
|
|
252
|
+
width: min(260px, 82vw);
|
|
253
|
+
z-index: 30;
|
|
254
|
+
transform: translateX(-100%);
|
|
255
|
+
transition: transform 0.18s ease;
|
|
256
|
+
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.app-sidebar.mobile-open {
|
|
260
|
+
transform: translateX(0);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.mobile-sidebar-overlay {
|
|
264
|
+
display: block;
|
|
265
|
+
position: fixed;
|
|
266
|
+
inset: 0 0 24px 0;
|
|
267
|
+
background: rgba(0, 0, 0, 0.45);
|
|
268
|
+
opacity: 0;
|
|
269
|
+
pointer-events: none;
|
|
270
|
+
transition: opacity 0.18s ease;
|
|
271
|
+
z-index: 20;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.mobile-sidebar-overlay.active {
|
|
275
|
+
opacity: 1;
|
|
276
|
+
pointer-events: auto;
|
|
277
|
+
}
|
|
183
278
|
}
|
package/lib/public/css/theme.css
CHANGED
|
@@ -156,6 +156,32 @@ textarea:focus {
|
|
|
156
156
|
background: rgba(99, 235, 255, 0.08);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
.ac-btn-secondary {
|
|
160
|
+
border: 1px solid var(--panel-border-contrast);
|
|
161
|
+
color: #d1d5db;
|
|
162
|
+
background: rgba(255, 255, 255, 0.03);
|
|
163
|
+
transition:
|
|
164
|
+
border-color 0.15s ease,
|
|
165
|
+
color 0.15s ease,
|
|
166
|
+
background 0.15s ease,
|
|
167
|
+
transform 0.15s ease;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.ac-btn-secondary:hover:not(:disabled) {
|
|
171
|
+
border-color: rgba(255, 255, 255, 0.35);
|
|
172
|
+
color: #f3f4f6;
|
|
173
|
+
background: rgba(255, 255, 255, 0.06);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.ac-btn-secondary:active:not(:disabled) {
|
|
177
|
+
transform: translateY(1px);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.ac-btn-secondary:disabled {
|
|
181
|
+
opacity: 0.5;
|
|
182
|
+
cursor: not-allowed;
|
|
183
|
+
}
|
|
184
|
+
|
|
159
185
|
.ac-btn-green {
|
|
160
186
|
border: 1px solid rgba(34, 197, 94, 0.45);
|
|
161
187
|
background: linear-gradient(
|
package/lib/public/js/app.js
CHANGED
|
@@ -30,6 +30,7 @@ import { Envars } from "./components/envars.js";
|
|
|
30
30
|
import { ToastContainer, showToast } from "./components/toast.js";
|
|
31
31
|
import { TelegramWorkspace } from "./components/telegram-workspace.js";
|
|
32
32
|
import { ChevronDownIcon } from "./components/icons.js";
|
|
33
|
+
import { UpdateActionButton } from "./components/update-action-button.js";
|
|
33
34
|
const html = htm.bind(h);
|
|
34
35
|
const kUiTabs = ["general", "providers", "envars"];
|
|
35
36
|
const kSubScreens = ["telegram"];
|
|
@@ -236,8 +237,8 @@ const GeneralTab = ({ onSwitchTab, onNavigate, isActive }) => {
|
|
|
236
237
|
<div>
|
|
237
238
|
<h2 class="font-semibold text-sm">OpenClaw Gateway Dashboard</h2>
|
|
238
239
|
</div>
|
|
239
|
-
|
|
240
|
-
|
|
240
|
+
<${UpdateActionButton}
|
|
241
|
+
onClick=${async () => {
|
|
241
242
|
if (dashboardLoading) return;
|
|
242
243
|
setDashboardLoading(true);
|
|
243
244
|
try {
|
|
@@ -250,13 +251,11 @@ const GeneralTab = ({ onSwitchTab, onNavigate, isActive }) => {
|
|
|
250
251
|
}
|
|
251
252
|
setDashboardLoading(false);
|
|
252
253
|
}}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
${dashboardLoading ? "Opening..." : "Open"}
|
|
259
|
-
</button>
|
|
254
|
+
loading=${dashboardLoading}
|
|
255
|
+
warning=${false}
|
|
256
|
+
idleLabel="Open"
|
|
257
|
+
loadingLabel="Opening..."
|
|
258
|
+
/>
|
|
260
259
|
</div>
|
|
261
260
|
<${DevicePairings}
|
|
262
261
|
pending=${devicePending}
|
|
@@ -298,6 +297,8 @@ function App() {
|
|
|
298
297
|
const [acDismissed, setAcDismissed] = useState(false);
|
|
299
298
|
const [authEnabled, setAuthEnabled] = useState(false);
|
|
300
299
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
300
|
+
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
301
|
+
const [mobileTopbarScrolled, setMobileTopbarScrolled] = useState(false);
|
|
301
302
|
const menuRef = useRef(null);
|
|
302
303
|
|
|
303
304
|
const closeMenu = useCallback((e) => {
|
|
@@ -326,6 +327,15 @@ function App() {
|
|
|
326
327
|
history.replaceState(null, "", `#${subScreen || tab}`);
|
|
327
328
|
}, [tab, subScreen]);
|
|
328
329
|
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
if (!mobileSidebarOpen) return;
|
|
332
|
+
const previousOverflow = document.body.style.overflow;
|
|
333
|
+
document.body.style.overflow = "hidden";
|
|
334
|
+
return () => {
|
|
335
|
+
document.body.style.overflow = previousOverflow;
|
|
336
|
+
};
|
|
337
|
+
}, [mobileSidebarOpen]);
|
|
338
|
+
|
|
329
339
|
useEffect(() => {
|
|
330
340
|
if (!onboarded) return;
|
|
331
341
|
let active = true;
|
|
@@ -363,7 +373,6 @@ function App() {
|
|
|
363
373
|
setAcUpdating(false);
|
|
364
374
|
}
|
|
365
375
|
};
|
|
366
|
-
|
|
367
376
|
// Still loading onboard status
|
|
368
377
|
if (onboarded === null) {
|
|
369
378
|
return html`
|
|
@@ -408,8 +417,20 @@ function App() {
|
|
|
408
417
|
`;
|
|
409
418
|
}
|
|
410
419
|
|
|
411
|
-
const navigateToSubScreen = (screen) =>
|
|
412
|
-
|
|
420
|
+
const navigateToSubScreen = (screen) => {
|
|
421
|
+
setSubScreen(screen);
|
|
422
|
+
setMobileSidebarOpen(false);
|
|
423
|
+
};
|
|
424
|
+
const exitSubScreen = () => {
|
|
425
|
+
setSubScreen(null);
|
|
426
|
+
setMobileSidebarOpen(false);
|
|
427
|
+
};
|
|
428
|
+
const handleAppContentScroll = (e) => {
|
|
429
|
+
const nextScrolled = e.currentTarget.scrollTop > 0;
|
|
430
|
+
setMobileTopbarScrolled((currentScrolled) =>
|
|
431
|
+
currentScrolled === nextScrolled ? currentScrolled : nextScrolled,
|
|
432
|
+
);
|
|
433
|
+
};
|
|
413
434
|
|
|
414
435
|
const kNavItems = [
|
|
415
436
|
{ id: "general", label: "General" },
|
|
@@ -419,7 +440,7 @@ function App() {
|
|
|
419
440
|
|
|
420
441
|
return html`
|
|
421
442
|
<div class="app-shell">
|
|
422
|
-
<div class
|
|
443
|
+
<div class=${`app-sidebar ${mobileSidebarOpen ? "mobile-open" : ""}`}>
|
|
423
444
|
<div class="sidebar-brand">
|
|
424
445
|
<img src="./img/logo.svg" alt="" width="20" height="20" />
|
|
425
446
|
<span><span style="color: var(--accent)">alpha</span>claw</span>
|
|
@@ -458,7 +479,11 @@ function App() {
|
|
|
458
479
|
(item) => html`
|
|
459
480
|
<a
|
|
460
481
|
class=${tab === item.id && !subScreen ? "active" : ""}
|
|
461
|
-
onclick=${() => {
|
|
482
|
+
onclick=${() => {
|
|
483
|
+
setSubScreen(null);
|
|
484
|
+
setTab(item.id);
|
|
485
|
+
setMobileSidebarOpen(false);
|
|
486
|
+
}}
|
|
462
487
|
>
|
|
463
488
|
${item.label}
|
|
464
489
|
</a>
|
|
@@ -468,19 +493,42 @@ function App() {
|
|
|
468
493
|
<div class="sidebar-footer">
|
|
469
494
|
${acHasUpdate && acLatest && !acDismissed
|
|
470
495
|
? html`
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
496
|
+
<${UpdateActionButton}
|
|
497
|
+
onClick=${handleAcUpdate}
|
|
498
|
+
loading=${acUpdating}
|
|
499
|
+
warning=${true}
|
|
500
|
+
idleLabel=${`Update to v${acLatest}`}
|
|
501
|
+
loadingLabel="Updating..."
|
|
502
|
+
className="w-full justify-center"
|
|
503
|
+
/>
|
|
478
504
|
`
|
|
479
505
|
: null}
|
|
480
506
|
</div>
|
|
481
507
|
</div>
|
|
482
508
|
|
|
483
|
-
<div
|
|
509
|
+
<div
|
|
510
|
+
class=${`mobile-sidebar-overlay ${mobileSidebarOpen ? "active" : ""}`}
|
|
511
|
+
onclick=${() => setMobileSidebarOpen(false)}
|
|
512
|
+
/>
|
|
513
|
+
|
|
514
|
+
<div class="app-content" onscroll=${handleAppContentScroll}>
|
|
515
|
+
<div class=${`mobile-topbar ${mobileTopbarScrolled ? "is-scrolled" : ""}`}>
|
|
516
|
+
<button
|
|
517
|
+
class="mobile-topbar-menu"
|
|
518
|
+
onclick=${() => setMobileSidebarOpen((open) => !open)}
|
|
519
|
+
aria-label="Open menu"
|
|
520
|
+
aria-expanded=${mobileSidebarOpen ? "true" : "false"}
|
|
521
|
+
>
|
|
522
|
+
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor">
|
|
523
|
+
<path
|
|
524
|
+
d="M2 3.75a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H2.75A.75.75 0 012 3.75zm0 4.25a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H2.75A.75.75 0 012 8zm0 4.25a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75z"
|
|
525
|
+
/>
|
|
526
|
+
</svg>
|
|
527
|
+
</button>
|
|
528
|
+
<span class="mobile-topbar-title">
|
|
529
|
+
<span style="color: var(--accent)">alpha</span>claw
|
|
530
|
+
</span>
|
|
531
|
+
</div>
|
|
484
532
|
<div class="max-w-2xl w-full mx-auto">
|
|
485
533
|
${subScreen === "telegram"
|
|
486
534
|
? html`
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import { useEffect } from "https://esm.sh/preact/hooks";
|
|
3
|
+
import htm from "https://esm.sh/htm";
|
|
4
|
+
|
|
5
|
+
const html = htm.bind(h);
|
|
6
|
+
|
|
7
|
+
export const ConfirmDialog = ({
|
|
8
|
+
visible = false,
|
|
9
|
+
title = "Confirm action",
|
|
10
|
+
message = "Are you sure you want to continue?",
|
|
11
|
+
confirmLabel = "Confirm",
|
|
12
|
+
cancelLabel = "Cancel",
|
|
13
|
+
onConfirm,
|
|
14
|
+
onCancel,
|
|
15
|
+
confirmTone = "primary",
|
|
16
|
+
}) => {
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!visible) return;
|
|
19
|
+
|
|
20
|
+
const handleKeydown = (event) => {
|
|
21
|
+
if (event.key === "Escape") {
|
|
22
|
+
onCancel?.();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
window.addEventListener("keydown", handleKeydown);
|
|
27
|
+
return () => window.removeEventListener("keydown", handleKeydown);
|
|
28
|
+
}, [visible, onCancel]);
|
|
29
|
+
|
|
30
|
+
if (!visible) return null;
|
|
31
|
+
|
|
32
|
+
const confirmClass =
|
|
33
|
+
confirmTone === "warning"
|
|
34
|
+
? "border border-yellow-500/45 text-yellow-300 bg-[linear-gradient(180deg,rgba(234,179,8,0.22)_0%,rgba(234,179,8,0.12)_100%)] shadow-[inset_0_0_0_1px_rgba(234,179,8,0.18)] hover:border-yellow-300/75 hover:text-yellow-200 hover:bg-[linear-gradient(180deg,rgba(234,179,8,0.3)_0%,rgba(234,179,8,0.16)_100%)] hover:shadow-[inset_0_0_0_1px_rgba(234,179,8,0.26),0_0_12px_rgba(234,179,8,0.16)]"
|
|
35
|
+
: "ac-btn-cyan";
|
|
36
|
+
|
|
37
|
+
return html`
|
|
38
|
+
<div
|
|
39
|
+
class="fixed inset-0 bg-black/70 flex items-center justify-center p-4 z-50"
|
|
40
|
+
onclick=${(event) => {
|
|
41
|
+
if (event.target === event.currentTarget) onCancel?.();
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<div class="bg-modal border border-border rounded-xl p-5 max-w-md w-full space-y-3">
|
|
45
|
+
<h2 class="text-base font-semibold">${title}</h2>
|
|
46
|
+
<p class="text-sm text-gray-400">${message}</p>
|
|
47
|
+
<div class="pt-1 flex items-center justify-end gap-2">
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
onclick=${onCancel}
|
|
51
|
+
class="px-4 py-2 rounded-lg text-sm ac-btn-secondary"
|
|
52
|
+
>
|
|
53
|
+
${cancelLabel}
|
|
54
|
+
</button>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
onclick=${onConfirm}
|
|
58
|
+
class="px-4 py-2 rounded-lg text-sm transition-all ${confirmClass}"
|
|
59
|
+
>
|
|
60
|
+
${confirmLabel}
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
`;
|
|
66
|
+
};
|
|
@@ -328,7 +328,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
328
328
|
</button>
|
|
329
329
|
<button
|
|
330
330
|
onclick=${onClose}
|
|
331
|
-
class="px-4 py-2 rounded-lg text-sm ac-btn-
|
|
331
|
+
class="px-4 py-2 rounded-lg text-sm ac-btn-secondary"
|
|
332
332
|
>
|
|
333
333
|
Cancel
|
|
334
334
|
</button>
|
|
@@ -7,14 +7,39 @@ import {
|
|
|
7
7
|
updateOpenclaw,
|
|
8
8
|
} from "../lib/api.js";
|
|
9
9
|
import { showToast } from "./toast.js";
|
|
10
|
+
import { UpdateActionButton } from "./update-action-button.js";
|
|
11
|
+
import { ConfirmDialog } from "./confirm-dialog.js";
|
|
10
12
|
const html = htm.bind(h);
|
|
11
13
|
|
|
12
|
-
function VersionRow({ label, currentVersion, fetchVersion, applyUpdate
|
|
14
|
+
function VersionRow({ label, currentVersion, fetchVersion, applyUpdate }) {
|
|
13
15
|
const [checking, setChecking] = useState(false);
|
|
14
16
|
const [version, setVersion] = useState(currentVersion || null);
|
|
15
17
|
const [latestVersion, setLatestVersion] = useState(null);
|
|
16
18
|
const [hasUpdate, setHasUpdate] = useState(false);
|
|
17
19
|
const [error, setError] = useState("");
|
|
20
|
+
const [hasViewedChangelog, setHasViewedChangelog] = useState(false);
|
|
21
|
+
const [confirmWithoutChangelogOpen, setConfirmWithoutChangelogOpen] = useState(false);
|
|
22
|
+
const simulateUpdate = (() => {
|
|
23
|
+
try {
|
|
24
|
+
const params = new URLSearchParams(window.location.search);
|
|
25
|
+
return params.get("simulateUpdate") === "1";
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
30
|
+
const simulatedVersion = (() => {
|
|
31
|
+
if (!simulateUpdate) return null;
|
|
32
|
+
try {
|
|
33
|
+
const params = new URLSearchParams(window.location.search);
|
|
34
|
+
return params.get("simulateVersion") || "v0.0.0-preview";
|
|
35
|
+
} catch {
|
|
36
|
+
return "v0.0.0-preview";
|
|
37
|
+
}
|
|
38
|
+
})();
|
|
39
|
+
const effectiveHasUpdate = simulateUpdate || hasUpdate;
|
|
40
|
+
const effectiveLatestVersion = simulatedVersion || latestVersion;
|
|
41
|
+
const changelogUrl = "https://github.com/openclaw/openclaw/tags";
|
|
42
|
+
const showMobileUpdateRow = effectiveHasUpdate && effectiveLatestVersion;
|
|
18
43
|
|
|
19
44
|
useEffect(() => {
|
|
20
45
|
setVersion(currentVersion || null);
|
|
@@ -36,22 +61,30 @@ function VersionRow({ label, currentVersion, fetchVersion, applyUpdate, tagsUrl
|
|
|
36
61
|
}
|
|
37
62
|
};
|
|
38
63
|
load();
|
|
39
|
-
return () => {
|
|
64
|
+
return () => {
|
|
65
|
+
active = false;
|
|
66
|
+
};
|
|
40
67
|
}, []);
|
|
41
68
|
|
|
42
|
-
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!effectiveHasUpdate || !effectiveLatestVersion) {
|
|
71
|
+
setHasViewedChangelog(false);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setHasViewedChangelog(false);
|
|
75
|
+
}, [effectiveHasUpdate, effectiveLatestVersion]);
|
|
76
|
+
|
|
77
|
+
const runAction = async () => {
|
|
43
78
|
if (checking) return;
|
|
44
79
|
setChecking(true);
|
|
45
80
|
setError("");
|
|
46
81
|
try {
|
|
47
|
-
const data =
|
|
48
|
-
? await applyUpdate()
|
|
49
|
-
: await fetchVersion(true);
|
|
82
|
+
const data = effectiveHasUpdate ? await applyUpdate() : await fetchVersion(true);
|
|
50
83
|
setVersion(data.currentVersion || version);
|
|
51
84
|
setLatestVersion(data.latestVersion || null);
|
|
52
85
|
setHasUpdate(!!data.hasUpdate);
|
|
53
86
|
setError(data.ok ? "" : data.error || "");
|
|
54
|
-
if (
|
|
87
|
+
if (effectiveHasUpdate) {
|
|
55
88
|
if (!data.ok) {
|
|
56
89
|
showToast(data.error || `${label} update failed`, "error");
|
|
57
90
|
} else if (data.updated || data.restarting) {
|
|
@@ -65,42 +98,117 @@ function VersionRow({ label, currentVersion, fetchVersion, applyUpdate, tagsUrl
|
|
|
65
98
|
showToast(`Already at latest ${label} version`, "success");
|
|
66
99
|
}
|
|
67
100
|
} else if (data.hasUpdate && data.latestVersion) {
|
|
68
|
-
showToast(
|
|
101
|
+
showToast(
|
|
102
|
+
`${label} update available: ${data.latestVersion}`,
|
|
103
|
+
"warning",
|
|
104
|
+
);
|
|
69
105
|
} else {
|
|
70
106
|
showToast(`${label} is up to date`, "success");
|
|
71
107
|
}
|
|
72
108
|
} catch (err) {
|
|
73
|
-
setError(
|
|
74
|
-
|
|
109
|
+
setError(
|
|
110
|
+
err.message ||
|
|
111
|
+
(effectiveHasUpdate ? `Could not update ${label}` : "Could not check updates"),
|
|
112
|
+
);
|
|
113
|
+
showToast(
|
|
114
|
+
effectiveHasUpdate ? `Could not update ${label}` : "Could not check updates",
|
|
115
|
+
"error",
|
|
116
|
+
);
|
|
75
117
|
}
|
|
76
118
|
setChecking(false);
|
|
77
119
|
};
|
|
78
120
|
|
|
121
|
+
const handleAction = () => {
|
|
122
|
+
if (checking) return;
|
|
123
|
+
if (effectiveHasUpdate && effectiveLatestVersion && !hasViewedChangelog) {
|
|
124
|
+
setConfirmWithoutChangelogOpen(true);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
runAction();
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleConfirmWithoutChangelog = () => {
|
|
131
|
+
setConfirmWithoutChangelogOpen(false);
|
|
132
|
+
runAction();
|
|
133
|
+
};
|
|
134
|
+
|
|
79
135
|
return html`
|
|
80
136
|
<div class="flex items-center justify-between gap-3">
|
|
81
137
|
<div class="min-w-0">
|
|
82
|
-
<p class="text-
|
|
83
|
-
<span class="text-gray-500">${label}</span>${" "}${version
|
|
138
|
+
<p class="text-xs text-gray-300 truncate">
|
|
139
|
+
<span class="text-gray-500">${label}</span>${" "}${version
|
|
140
|
+
? `${version}`
|
|
141
|
+
: "..."}
|
|
84
142
|
</p>
|
|
85
143
|
${error && html`<p class="text-xs text-yellow-500 mt-1">${error}</p>`}
|
|
86
144
|
</div>
|
|
87
145
|
<div class="flex items-center gap-2 shrink-0">
|
|
88
|
-
${
|
|
89
|
-
<a
|
|
90
|
-
|
|
91
|
-
|
|
146
|
+
${effectiveHasUpdate && effectiveLatestVersion && html`
|
|
147
|
+
<a
|
|
148
|
+
href=${changelogUrl}
|
|
149
|
+
target="_blank"
|
|
150
|
+
rel="noreferrer"
|
|
151
|
+
onclick=${() => setHasViewedChangelog(true)}
|
|
152
|
+
class="hidden md:inline text-xs text-gray-500 hover:text-gray-300 transition-colors"
|
|
153
|
+
>View changelog</a
|
|
154
|
+
>
|
|
92
155
|
`}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
156
|
+
${showMobileUpdateRow
|
|
157
|
+
? html`
|
|
158
|
+
<${UpdateActionButton}
|
|
159
|
+
onClick=${handleAction}
|
|
160
|
+
loading=${checking}
|
|
161
|
+
warning=${effectiveHasUpdate}
|
|
162
|
+
idleLabel=${effectiveHasUpdate
|
|
163
|
+
? `Update to ${effectiveLatestVersion || "latest"}`
|
|
164
|
+
: "Check updates"}
|
|
165
|
+
loadingLabel=${effectiveHasUpdate ? "Updating..." : "Checking..."}
|
|
166
|
+
className="hidden md:inline-flex"
|
|
167
|
+
/>
|
|
168
|
+
`
|
|
169
|
+
: html`
|
|
170
|
+
<${UpdateActionButton}
|
|
171
|
+
onClick=${handleAction}
|
|
172
|
+
loading=${checking}
|
|
173
|
+
warning=${effectiveHasUpdate}
|
|
174
|
+
idleLabel=${effectiveHasUpdate
|
|
175
|
+
? `Update to ${effectiveLatestVersion || "latest"}`
|
|
176
|
+
: "Check updates"}
|
|
177
|
+
loadingLabel=${effectiveHasUpdate ? "Updating..." : "Checking..."}
|
|
178
|
+
/>
|
|
179
|
+
`}
|
|
102
180
|
</div>
|
|
103
181
|
</div>
|
|
182
|
+
${showMobileUpdateRow && html`
|
|
183
|
+
<div class="mt-2 md:hidden flex items-center gap-2">
|
|
184
|
+
<a
|
|
185
|
+
href=${changelogUrl}
|
|
186
|
+
target="_blank"
|
|
187
|
+
rel="noreferrer"
|
|
188
|
+
onclick=${() => setHasViewedChangelog(true)}
|
|
189
|
+
class="inline-flex items-center justify-center flex-1 h-9 text-xs rounded-lg border border-border text-gray-400 hover:text-gray-200 hover:border-gray-500 transition-colors"
|
|
190
|
+
>View changelog</a
|
|
191
|
+
>
|
|
192
|
+
<${UpdateActionButton}
|
|
193
|
+
onClick=${handleAction}
|
|
194
|
+
loading=${checking}
|
|
195
|
+
warning=${effectiveHasUpdate}
|
|
196
|
+
idleLabel=${`Update to ${effectiveLatestVersion || "latest"}`}
|
|
197
|
+
loadingLabel="Updating..."
|
|
198
|
+
className="flex-1 h-9 px-3"
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
`}
|
|
202
|
+
<${ConfirmDialog}
|
|
203
|
+
visible=${confirmWithoutChangelogOpen}
|
|
204
|
+
title="Update without changelog?"
|
|
205
|
+
message="Are you sure you want to update without viewing the changelog?"
|
|
206
|
+
confirmLabel=${`Update to ${effectiveLatestVersion || "latest"}`}
|
|
207
|
+
cancelLabel="Cancel"
|
|
208
|
+
confirmTone="warning"
|
|
209
|
+
onCancel=${() => setConfirmWithoutChangelogOpen(false)}
|
|
210
|
+
onConfirm=${handleConfirmWithoutChangelog}
|
|
211
|
+
/>
|
|
104
212
|
`;
|
|
105
213
|
}
|
|
106
214
|
|
|
@@ -134,16 +242,14 @@ export function Gateway({ status, openclawVersion }) {
|
|
|
134
242
|
>
|
|
135
243
|
</div>
|
|
136
244
|
</div>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
disabled=${
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
Restart
|
|
146
|
-
</button>
|
|
245
|
+
<${UpdateActionButton}
|
|
246
|
+
onClick=${handleRestart}
|
|
247
|
+
disabled=${!status}
|
|
248
|
+
loading=${restarting}
|
|
249
|
+
warning=${false}
|
|
250
|
+
idleLabel="Restart"
|
|
251
|
+
loadingLabel="On it..."
|
|
252
|
+
/>
|
|
147
253
|
</div>
|
|
148
254
|
<div class="mt-3 pt-3 border-t border-border">
|
|
149
255
|
<${VersionRow}
|
|
@@ -151,7 +257,6 @@ export function Gateway({ status, openclawVersion }) {
|
|
|
151
257
|
currentVersion=${openclawVersion}
|
|
152
258
|
fetchVersion=${fetchOpenclawVersion}
|
|
153
259
|
applyUpdate=${updateOpenclaw}
|
|
154
|
-
tagsUrl="https://github.com/openclaw/openclaw/tags"
|
|
155
260
|
/>
|
|
156
261
|
</div>
|
|
157
262
|
</div>`;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getDefaultScopes,
|
|
13
13
|
} from "./scope-picker.js";
|
|
14
14
|
import { CredentialsModal } from "./credentials-modal.js";
|
|
15
|
+
import { ConfirmDialog } from "./confirm-dialog.js";
|
|
15
16
|
import { showToast } from "./toast.js";
|
|
16
17
|
const html = htm.bind(h);
|
|
17
18
|
|
|
@@ -22,6 +23,7 @@ export function Google({ gatewayStatus }) {
|
|
|
22
23
|
const [apiStatus, setApiStatus] = useState({});
|
|
23
24
|
const [checkingApis, setCheckingApis] = useState(false);
|
|
24
25
|
const [modalOpen, setModalOpen] = useState(false);
|
|
26
|
+
const [disconnectDialogOpen, setDisconnectDialogOpen] = useState(false);
|
|
25
27
|
|
|
26
28
|
const runApiCheck = useCallback(async () => {
|
|
27
29
|
setApiStatus({});
|
|
@@ -98,12 +100,6 @@ export function Google({ gatewayStatus }) {
|
|
|
98
100
|
const handleCheckApis = () => runApiCheck();
|
|
99
101
|
|
|
100
102
|
const handleDisconnect = async () => {
|
|
101
|
-
if (
|
|
102
|
-
!confirm(
|
|
103
|
-
"Disconnect Google account? Your agent will lose access to Gmail, Calendar, etc.",
|
|
104
|
-
)
|
|
105
|
-
)
|
|
106
|
-
return;
|
|
107
103
|
const data = await apiDisconnect();
|
|
108
104
|
if (data.ok) {
|
|
109
105
|
setGoogle({
|
|
@@ -188,7 +184,7 @@ export function Google({ gatewayStatus }) {
|
|
|
188
184
|
</button>
|
|
189
185
|
</div>
|
|
190
186
|
<button
|
|
191
|
-
onclick=${
|
|
187
|
+
onclick=${() => setDisconnectDialogOpen(true)}
|
|
192
188
|
class="text-xs text-red-400/60 hover:text-red-400"
|
|
193
189
|
>
|
|
194
190
|
Disconnect
|
|
@@ -216,5 +212,17 @@ export function Google({ gatewayStatus }) {
|
|
|
216
212
|
onClose=${() => setModalOpen(false)}
|
|
217
213
|
onSaved=${refresh}
|
|
218
214
|
/>
|
|
215
|
+
<${ConfirmDialog}
|
|
216
|
+
visible=${disconnectDialogOpen}
|
|
217
|
+
title="Disconnect Google account?"
|
|
218
|
+
message="Your agent will lose access to Gmail, Calendar, and other Google Workspace services until you reconnect."
|
|
219
|
+
confirmLabel="Disconnect"
|
|
220
|
+
cancelLabel="Cancel"
|
|
221
|
+
onCancel=${() => setDisconnectDialogOpen(false)}
|
|
222
|
+
onConfirm=${async () => {
|
|
223
|
+
setDisconnectDialogOpen(false);
|
|
224
|
+
await handleDisconnect();
|
|
225
|
+
}}
|
|
226
|
+
/>
|
|
219
227
|
`;
|
|
220
228
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
|
|
4
|
+
const html = htm.bind(h);
|
|
5
|
+
|
|
6
|
+
export const UpdateActionButton = ({
|
|
7
|
+
onClick,
|
|
8
|
+
disabled = false,
|
|
9
|
+
loading = false,
|
|
10
|
+
warning = false,
|
|
11
|
+
idleLabel = "Check updates",
|
|
12
|
+
loadingLabel = "Checking...",
|
|
13
|
+
className = "",
|
|
14
|
+
}) => {
|
|
15
|
+
const isInteractive = !loading && !disabled;
|
|
16
|
+
const toneClass = warning
|
|
17
|
+
? isInteractive
|
|
18
|
+
? "border-yellow-500/35 text-yellow-400 bg-yellow-500/10 hover:border-yellow-400/60 hover:text-yellow-300 hover:bg-yellow-500/15"
|
|
19
|
+
: "border-yellow-500/35 text-yellow-400 bg-yellow-500/10"
|
|
20
|
+
: isInteractive
|
|
21
|
+
? "border-border text-gray-500 hover:text-gray-300 hover:border-gray-500"
|
|
22
|
+
: "border-border text-gray-500";
|
|
23
|
+
const loadingClass = loading
|
|
24
|
+
? `cursor-not-allowed ${warning
|
|
25
|
+
? "opacity-90 animate-pulse shadow-[0_0_0_1px_rgba(234,179,8,0.22),0_0_18px_rgba(234,179,8,0.12)]"
|
|
26
|
+
: "opacity-80"}`
|
|
27
|
+
: "";
|
|
28
|
+
|
|
29
|
+
return html`
|
|
30
|
+
<button
|
|
31
|
+
onclick=${onClick}
|
|
32
|
+
disabled=${disabled || loading}
|
|
33
|
+
class="inline-flex items-center justify-center h-7 text-xs leading-none px-2.5 py-1 rounded-lg border transition-colors whitespace-nowrap ${toneClass} ${loadingClass} ${className}"
|
|
34
|
+
>
|
|
35
|
+
${loading
|
|
36
|
+
? html`
|
|
37
|
+
<span class="inline-flex items-center gap-1.5 leading-none">
|
|
38
|
+
<svg
|
|
39
|
+
class="animate-spin"
|
|
40
|
+
width="12"
|
|
41
|
+
height="12"
|
|
42
|
+
viewBox="0 0 24 24"
|
|
43
|
+
fill="none"
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
>
|
|
46
|
+
<circle
|
|
47
|
+
class="opacity-30"
|
|
48
|
+
cx="12"
|
|
49
|
+
cy="12"
|
|
50
|
+
r="9"
|
|
51
|
+
stroke="currentColor"
|
|
52
|
+
stroke-width="3"
|
|
53
|
+
/>
|
|
54
|
+
<path
|
|
55
|
+
class="opacity-90"
|
|
56
|
+
fill="currentColor"
|
|
57
|
+
d="M12 3a9 9 0 0 1 9 9h-3a6 6 0 0 0-6-6V3z"
|
|
58
|
+
/>
|
|
59
|
+
</svg>
|
|
60
|
+
${loadingLabel}
|
|
61
|
+
</span>
|
|
62
|
+
`
|
|
63
|
+
: idleLabel}
|
|
64
|
+
</button>
|
|
65
|
+
`;
|
|
66
|
+
};
|
package/lib/public/login.html
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap"
|
|
9
9
|
rel="stylesheet"
|
|
10
10
|
/>
|
|
11
|
+
<link rel="icon" type="image/svg+xml" href="./img/logo.svg" />
|
|
11
12
|
<link rel="stylesheet" href="./css/theme.css" />
|
|
12
13
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
13
14
|
<script>
|
package/lib/public/setup.html
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>alphaclaw</title>
|
|
7
7
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="./img/logo.svg">
|
|
8
9
|
<link rel="stylesheet" href="./css/theme.css">
|
|
9
10
|
<link rel="stylesheet" href="./css/shell.css">
|
|
10
11
|
<script src="https://cdn.tailwindcss.com"></script>
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrysb/alphaclaw",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
7
|
"description": "Setup UI, gateway manager, and onboarding wrapper for OpenClaw",
|
|
8
8
|
"bin": {
|
|
9
|
-
"alphaclaw": "
|
|
9
|
+
"alphaclaw": "bin/alphaclaw.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"bin/",
|