@chrysb/alphaclaw 0.3.5-beta.1 → 0.4.1-beta.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 (68) hide show
  1. package/bin/alphaclaw.js +1 -31
  2. package/lib/public/assets/icons/google_icon.svg +8 -0
  3. package/lib/public/css/explorer.css +53 -0
  4. package/lib/public/css/shell.css +21 -19
  5. package/lib/public/css/theme.css +17 -0
  6. package/lib/public/js/app.js +205 -109
  7. package/lib/public/js/components/credentials-modal.js +36 -8
  8. package/lib/public/js/components/file-tree.js +212 -22
  9. package/lib/public/js/components/file-viewer/editor-surface.js +5 -0
  10. package/lib/public/js/components/file-viewer/index.js +47 -6
  11. package/lib/public/js/components/file-viewer/markdown-split-view.js +2 -0
  12. package/lib/public/js/components/file-viewer/status-banners.js +11 -6
  13. package/lib/public/js/components/file-viewer/toolbar.js +56 -1
  14. package/lib/public/js/components/file-viewer/use-editor-selection-restore.js +6 -0
  15. package/lib/public/js/components/file-viewer/use-file-diff.js +11 -0
  16. package/lib/public/js/components/file-viewer/use-file-loader.js +12 -2
  17. package/lib/public/js/components/file-viewer/use-file-viewer.js +142 -15
  18. package/lib/public/js/components/google/account-row.js +131 -0
  19. package/lib/public/js/components/google/add-account-modal.js +93 -0
  20. package/lib/public/js/components/google/gmail-setup-wizard.js +450 -0
  21. package/lib/public/js/components/google/gmail-watch-toggle.js +81 -0
  22. package/lib/public/js/components/google/index.js +553 -0
  23. package/lib/public/js/components/google/use-gmail-watch.js +140 -0
  24. package/lib/public/js/components/google/use-google-accounts.js +41 -0
  25. package/lib/public/js/components/icons.js +26 -0
  26. package/lib/public/js/components/scope-picker.js +1 -1
  27. package/lib/public/js/components/sidebar-git-panel.js +48 -20
  28. package/lib/public/js/components/sidebar.js +93 -75
  29. package/lib/public/js/components/toast.js +11 -7
  30. package/lib/public/js/components/usage-tab/constants.js +31 -0
  31. package/lib/public/js/components/usage-tab/formatters.js +24 -0
  32. package/lib/public/js/components/usage-tab/index.js +72 -0
  33. package/lib/public/js/components/usage-tab/overview-section.js +147 -0
  34. package/lib/public/js/components/usage-tab/sessions-section.js +175 -0
  35. package/lib/public/js/components/usage-tab/use-usage-tab.js +241 -0
  36. package/lib/public/js/components/webhooks.js +182 -129
  37. package/lib/public/js/lib/api.js +178 -9
  38. package/lib/public/js/lib/browse-file-policies.js +29 -11
  39. package/lib/public/js/lib/format.js +71 -0
  40. package/lib/public/js/lib/syntax-highlighters/index.js +6 -5
  41. package/lib/public/shared/browse-file-policies.json +13 -0
  42. package/lib/server/constants.js +47 -7
  43. package/lib/server/gmail-push.js +109 -0
  44. package/lib/server/gmail-serve.js +254 -0
  45. package/lib/server/gmail-watch.js +725 -0
  46. package/lib/server/google-state.js +317 -0
  47. package/lib/server/helpers.js +17 -11
  48. package/lib/server/internal-files-migration.js +31 -3
  49. package/lib/server/onboarding/github.js +21 -2
  50. package/lib/server/onboarding/index.js +1 -3
  51. package/lib/server/onboarding/openclaw.js +3 -0
  52. package/lib/server/onboarding/workspace.js +40 -0
  53. package/lib/server/routes/browse/index.js +90 -2
  54. package/lib/server/routes/gmail.js +128 -0
  55. package/lib/server/routes/google.js +433 -213
  56. package/lib/server/routes/system.js +107 -0
  57. package/lib/server/routes/usage.js +29 -2
  58. package/lib/server/routes/webhooks.js +52 -17
  59. package/lib/server/usage-db.js +283 -15
  60. package/lib/server/watchdog.js +66 -0
  61. package/lib/server/webhook-middleware.js +99 -1
  62. package/lib/server/webhooks.js +214 -65
  63. package/lib/server.js +27 -0
  64. package/lib/setup/gitignore +6 -0
  65. package/lib/setup/hourly-git-sync.sh +29 -2
  66. package/package.json +1 -1
  67. package/lib/public/js/components/google.js +0 -228
  68. package/lib/public/js/components/usage-tab.js +0 -531
package/bin/alphaclaw.js CHANGED
@@ -626,37 +626,7 @@ try {
626
626
  } catch {}
627
627
 
628
628
  // ---------------------------------------------------------------------------
629
- // 10. Configure gog credentials (if env vars present)
630
- // ---------------------------------------------------------------------------
631
-
632
- if (process.env.GOG_CLIENT_CREDENTIALS_JSON && process.env.GOG_REFRESH_TOKEN) {
633
- try {
634
- const tmpCreds = `/tmp/gog-creds-${process.pid}.json`;
635
- const tmpToken = `/tmp/gog-token-${process.pid}.json`;
636
- fs.writeFileSync(tmpCreds, process.env.GOG_CLIENT_CREDENTIALS_JSON);
637
- execSync(`gog auth credentials set "${tmpCreds}"`, { stdio: "ignore" });
638
- fs.unlinkSync(tmpCreds);
639
- fs.writeFileSync(
640
- tmpToken,
641
- JSON.stringify({
642
- email: process.env.GOG_ACCOUNT || "",
643
- refresh_token: process.env.GOG_REFRESH_TOKEN,
644
- }),
645
- );
646
- execSync(`gog auth tokens import "${tmpToken}"`, { stdio: "ignore" });
647
- fs.unlinkSync(tmpToken);
648
- console.log(
649
- `[alphaclaw] gog CLI configured for ${process.env.GOG_ACCOUNT || "account"}`,
650
- );
651
- } catch (e) {
652
- console.log(`[alphaclaw] gog credentials setup skipped: ${e.message}`);
653
- }
654
- } else {
655
- console.log("[alphaclaw] Google credentials not set -- skipping gog setup");
656
- }
657
-
658
- // ---------------------------------------------------------------------------
659
- // 11. Reconcile channels if already onboarded
629
+ // 10. Reconcile channels if already onboarded
660
630
  // ---------------------------------------------------------------------------
661
631
 
662
632
  const configPath = path.join(openclawDir, "openclaw.json");
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-labelledby="title">
2
+ <title>Google</title>
3
+ <path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
4
+ <path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
5
+ <path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
6
+ <path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
7
+ <path d="M1 1h22v22H1z" fill="none"/>
8
+ </svg>
@@ -216,6 +216,28 @@
216
216
  background: var(--bg-hover);
217
217
  }
218
218
 
219
+ .tree-folder.active {
220
+ background: var(--bg-active);
221
+ color: var(--accent);
222
+ }
223
+
224
+ .tree-folder.active .arrow {
225
+ color: var(--accent);
226
+ }
227
+
228
+ .tree-folder-toggle {
229
+ border: 0;
230
+ margin: 0;
231
+ padding: 0;
232
+ background: transparent;
233
+ color: inherit;
234
+ display: inline-flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ cursor: pointer;
238
+ flex: 0 0 auto;
239
+ }
240
+
219
241
  .arrow {
220
242
  font-size: 10px;
221
243
  transition: transform 0.15s;
@@ -565,6 +587,37 @@
565
587
  flex-shrink: 0;
566
588
  }
567
589
 
590
+ .file-viewer-icon-action {
591
+ display: inline-flex;
592
+ align-items: center;
593
+ justify-content: center;
594
+ width: 28px;
595
+ height: 28px;
596
+ margin-right: 10px;
597
+ border: 1px solid var(--border);
598
+ border-radius: 8px;
599
+ background: rgba(255, 255, 255, 0.02);
600
+ color: var(--text-muted);
601
+ transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
602
+ }
603
+
604
+ .file-viewer-icon-action:hover {
605
+ border-color: rgba(148, 163, 184, 0.45);
606
+ color: var(--text);
607
+ background: rgba(148, 163, 184, 0.08);
608
+ }
609
+
610
+ .file-viewer-icon-action.is-disabled {
611
+ opacity: 0.45;
612
+ cursor: not-allowed;
613
+ }
614
+
615
+ .file-viewer-icon-action-icon {
616
+ width: 14px;
617
+ height: 14px;
618
+ flex-shrink: 0;
619
+ }
620
+
568
621
  .file-viewer-view-toggle {
569
622
  display: flex;
570
623
  align-items: center;
@@ -11,35 +11,37 @@
11
11
  }
12
12
 
13
13
  .global-restart-banner {
14
- grid-column: 1 / -1;
15
- grid-row: 1;
16
- background: rgba(234, 179, 8, 0.08);
17
- border-bottom: 1px solid rgba(234, 179, 8, 0.32);
18
- padding: 10px 32px;
14
+ position: fixed;
15
+ left: 50%;
16
+ bottom: 52px;
17
+ transform: translateX(-50%);
18
+ width: auto;
19
+ max-width: calc(100vw - 32px);
20
+ z-index: 40;
21
+ pointer-events: none;
19
22
  }
20
23
 
21
24
  .global-restart-banner__content {
22
- margin: 0 auto;
23
- max-width: 900px;
24
- position: relative;
25
+ border: 1px solid rgba(234, 179, 8, 0.35);
26
+ background: rgba(43, 32, 6, 0.95);
27
+ box-shadow: 0 18px 46px rgba(0, 0, 0, 0.42);
28
+ border-radius: 14px;
29
+ padding: 10px 14px;
25
30
  display: flex;
26
31
  align-items: center;
27
- justify-content: center;
32
+ justify-content: space-between;
28
33
  gap: 12px;
29
- min-height: 28px;
34
+ pointer-events: auto;
30
35
  }
31
36
 
32
37
  .global-restart-banner__text {
33
38
  font-size: 12px;
34
39
  color: #fde68a;
35
- text-align: center;
40
+ line-height: 1.4;
36
41
  }
37
42
 
38
43
  .global-restart-banner__button {
39
- position: absolute;
40
- right: 0;
41
- top: 50%;
42
- transform: translateY(-50%);
44
+ flex-shrink: 0;
43
45
  }
44
46
 
45
47
  .app-content {
@@ -292,13 +294,13 @@
292
294
  grid-template-rows: auto 1fr 24px;
293
295
  }
294
296
  .global-restart-banner {
295
- padding: 10px 14px;
297
+ max-width: calc(100vw - 20px);
298
+ bottom: 44px;
296
299
  }
297
300
  .global-restart-banner__content {
298
- max-width: none;
299
- align-items: flex-start;
301
+ align-items: stretch;
300
302
  flex-direction: column;
301
- min-height: 0;
303
+ gap: 8px;
302
304
  }
303
305
  .global-restart-banner__text {
304
306
  text-align: left;
@@ -66,6 +66,13 @@ body::before {
66
66
  background: rgba(0, 0, 0, 0.12);
67
67
  }
68
68
 
69
+ /* Shared inset panel for "surface on surface" layouts. */
70
+ .ac-surface-inset {
71
+ border: 1px solid var(--panel-border-contrast);
72
+ border-radius: 10px;
73
+ background: rgba(0, 0, 0, 0.12);
74
+ }
75
+
69
76
  .ac-history-summary {
70
77
  cursor: pointer;
71
78
  list-style: none;
@@ -116,6 +123,16 @@ body::before {
116
123
  border-color: var(--panel-border-contrast) !important;
117
124
  }
118
125
 
126
+ .ac-tip-link {
127
+ color: var(--accent-link);
128
+ text-decoration: underline;
129
+ text-underline-offset: 2px;
130
+ }
131
+
132
+ .ac-tip-link:hover {
133
+ color: var(--accent);
134
+ }
135
+
119
136
  /* Universal field contrast treatment (all tabs/pages). */
120
137
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
121
138
  select,
@@ -28,7 +28,7 @@ import { Gateway } from "./components/gateway.js";
28
28
  import { Channels, ALL_CHANNELS } from "./components/channels.js";
29
29
  import { Pairings } from "./components/pairings.js";
30
30
  import { DevicePairings } from "./components/device-pairings.js";
31
- import { Google } from "./components/google.js";
31
+ import { Google } from "./components/google/index.js";
32
32
  import { Features } from "./components/features.js";
33
33
  import { Providers } from "./components/providers.js";
34
34
  import { Welcome } from "./components/welcome.js";
@@ -43,7 +43,7 @@ import { LoadingSpinner } from "./components/loading-spinner.js";
43
43
  import { WatchdogTab } from "./components/watchdog-tab.js";
44
44
  import { FileViewer } from "./components/file-viewer/index.js";
45
45
  import { AppSidebar } from "./components/sidebar.js";
46
- import { UsageTab } from "./components/usage-tab.js";
46
+ import { UsageTab } from "./components/usage-tab/index.js";
47
47
  import { readUiSettings, writeUiSettings } from "./lib/ui-settings.js";
48
48
  const html = htm.bind(h);
49
49
  const kDefaultUiTab = "general";
@@ -52,6 +52,32 @@ const kSidebarMinWidthPx = 180;
52
52
  const kSidebarMaxWidthPx = 460;
53
53
  const kBrowseLastPathUiSettingKey = "browseLastPath";
54
54
  const kLastMenuRouteUiSettingKey = "lastMenuRoute";
55
+ const kBrowseRestartRequiredRules = [
56
+ { type: "file", path: "openclaw.json" },
57
+ { type: "directory", path: "hooks/transforms" },
58
+ ];
59
+ const normalizeBrowsePath = (value) => String(value || "").replace(/^\/+|\/+$/g, "");
60
+ const normalizeRestartRulePath = (value) =>
61
+ String(value || "")
62
+ .trim()
63
+ .replace(/^\/+|\/+$/g, "");
64
+ const matchesBrowseRestartRequiredRule = (path, rule) => {
65
+ const normalizedPath = normalizeRestartRulePath(path);
66
+ if (!normalizedPath) return false;
67
+ if (!rule || typeof rule !== "object") return false;
68
+ const type = String(rule.type || "").toLowerCase();
69
+ const targetPath = normalizeRestartRulePath(rule.path);
70
+ if (!targetPath) return false;
71
+ if (type === "directory") {
72
+ return normalizedPath === targetPath || normalizedPath.startsWith(`${targetPath}/`);
73
+ }
74
+ if (type === "file") {
75
+ return normalizedPath === targetPath;
76
+ }
77
+ return false;
78
+ };
79
+ const shouldRequireRestartForBrowsePath = (path) =>
80
+ kBrowseRestartRequiredRules.some((rule) => matchesBrowseRestartRequiredRule(path, rule));
55
81
 
56
82
  const clampSidebarWidth = (value) =>
57
83
  Math.max(kSidebarMinWidthPx, Math.min(kSidebarMaxWidthPx, value));
@@ -98,6 +124,7 @@ const GeneralTab = ({
98
124
  onRefreshStatuses = () => {},
99
125
  onSwitchTab,
100
126
  onNavigate,
127
+ onOpenGmailWebhook = () => {},
101
128
  isActive,
102
129
  restartingGateway,
103
130
  onRestartGateway,
@@ -105,8 +132,8 @@ const GeneralTab = ({
105
132
  openclawUpdateInProgress = false,
106
133
  onOpenclawVersionActionComplete = () => {},
107
134
  onOpenclawUpdate,
135
+ onRestartRequired = () => {},
108
136
  }) => {
109
- const [googleKey, setGoogleKey] = useState(0);
110
137
  const [dashboardLoading, setDashboardLoading] = useState(false);
111
138
  const [repairingWatchdog, setRepairingWatchdog] = useState(false);
112
139
  const status = statusData;
@@ -179,7 +206,6 @@ const GeneralTab = ({
179
206
  onRefreshStatuses();
180
207
  pairingsPoll.refresh();
181
208
  devicePoll.refresh();
182
- setGoogleKey((k) => k + 1);
183
209
  }, [isActive]);
184
210
 
185
211
  useEffect(() => {
@@ -280,7 +306,11 @@ const GeneralTab = ({
280
306
  onReject=${handleReject}
281
307
  />
282
308
  <${Features} onSwitchTab=${onSwitchTab} />
283
- <${Google} key=${googleKey} gatewayStatus=${gatewayStatus} />
309
+ <${Google}
310
+ gatewayStatus=${gatewayStatus}
311
+ onRestartRequired=${onRestartRequired}
312
+ onOpenGmailWebhook=${onOpenGmailWebhook}
313
+ />
284
314
 
285
315
  ${repo &&
286
316
  html`
@@ -421,11 +451,13 @@ const App = () => {
421
451
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
422
452
  const [mobileTopbarScrolled, setMobileTopbarScrolled] = useState(false);
423
453
  const [restartRequired, setRestartRequired] = useState(false);
454
+ const [browseRestartRequired, setBrowseRestartRequired] = useState(false);
424
455
  const [restartingGateway, setRestartingGateway] = useState(false);
425
456
  const [gatewayRestartSignal, setGatewayRestartSignal] = useState(0);
426
457
  const [statusPollCadenceMs, setStatusPollCadenceMs] = useState(15000);
427
458
  const [openclawUpdateInProgress, setOpenclawUpdateInProgress] = useState(false);
428
459
  const menuRef = useRef(null);
460
+ const routeHistoryRef = useRef([]);
429
461
  const sharedStatusPoll = usePolling(fetchStatus, statusPollCadenceMs, {
430
462
  enabled: onboarded === true,
431
463
  });
@@ -434,6 +466,7 @@ const App = () => {
434
466
  });
435
467
  const sharedStatus = sharedStatusPoll.data || null;
436
468
  const sharedWatchdogStatus = sharedWatchdogPoll.data?.status || null;
469
+ const isAnyRestartRequired = restartRequired || browseRestartRequired;
437
470
  const refreshSharedStatuses = useCallback(() => {
438
471
  sharedStatusPoll.refresh();
439
472
  sharedWatchdogPoll.refresh();
@@ -537,6 +570,18 @@ const App = () => {
537
570
  return () => clearInterval(id);
538
571
  }, [onboarded, restartRequired, restartingGateway, refreshRestartStatus]);
539
572
 
573
+ useEffect(() => {
574
+ const handleBrowseFileSaved = (event) => {
575
+ const savedPath = String(event?.detail?.path || "");
576
+ if (!shouldRequireRestartForBrowsePath(savedPath)) return;
577
+ setBrowseRestartRequired(true);
578
+ };
579
+ window.addEventListener("alphaclaw:browse-file-saved", handleBrowseFileSaved);
580
+ return () => {
581
+ window.removeEventListener("alphaclaw:browse-file-saved", handleBrowseFileSaved);
582
+ };
583
+ }, []);
584
+
540
585
  const handleGatewayRestart = useCallback(async () => {
541
586
  if (restartingGateway) return;
542
587
  setRestartingGateway(true);
@@ -544,6 +589,7 @@ const App = () => {
544
589
  const data = await restartGateway();
545
590
  if (!data?.ok) throw new Error(data?.error || "Gateway restart failed");
546
591
  setRestartRequired(!!data.restartRequired);
592
+ setBrowseRestartRequired(false);
547
593
  setGatewayRestartSignal(Date.now());
548
594
  refreshSharedStatuses();
549
595
  showToast("Gateway restarted", "success");
@@ -646,9 +692,27 @@ const App = () => {
646
692
  setLocation(`/${screen}`);
647
693
  setMobileSidebarOpen(false);
648
694
  };
695
+ const handleBrowsePreviewFile = useCallback((nextPreviewPath) => {
696
+ const normalizedPreviewPath = normalizeBrowsePath(nextPreviewPath);
697
+ setBrowsePreviewPath(normalizedPreviewPath);
698
+ }, []);
649
699
  const navigateToBrowseFile = (relativePath, options = {}) => {
650
- setBrowsePreviewPath("");
651
- setLocation(buildBrowseRoute(relativePath, options));
700
+ const normalizedTargetPath = normalizeBrowsePath(relativePath);
701
+ const selectingDirectory =
702
+ !!options.directory || String(relativePath || "").trim().endsWith("/");
703
+ const shouldPreservePreview = selectingDirectory && !!options.preservePreview;
704
+ const activePath = normalizeBrowsePath(
705
+ browsePreviewPath || selectedBrowsePath || "",
706
+ );
707
+ const nextPreviewPath =
708
+ shouldPreservePreview && activePath && activePath !== normalizedTargetPath
709
+ ? activePath
710
+ : "";
711
+ setBrowsePreviewPath(nextPreviewPath);
712
+ const routeOptions = selectingDirectory
713
+ ? { ...options, view: "edit" }
714
+ : options;
715
+ setLocation(buildBrowseRoute(normalizedTargetPath, routeOptions));
652
716
  setMobileSidebarOpen(false);
653
717
  };
654
718
  const handleSidebarLogout = async () => {
@@ -765,6 +829,16 @@ const App = () => {
765
829
  setBrowsePreviewPath("");
766
830
  }, [location]);
767
831
 
832
+ useEffect(() => {
833
+ const historyStack = routeHistoryRef.current;
834
+ const lastEntry = historyStack[historyStack.length - 1];
835
+ if (lastEntry === location) return;
836
+ historyStack.push(location);
837
+ if (historyStack.length > 100) {
838
+ historyStack.shift();
839
+ }
840
+ }, [location]);
841
+
768
842
  useEffect(() => {
769
843
  if (location.startsWith("/browse")) return;
770
844
  if (location === "/telegram") return;
@@ -848,8 +922,24 @@ const App = () => {
848
922
  <${Webhooks}
849
923
  selectedHookName=${hookName}
850
924
  onSelectHook=${(name) => setLocation(`/webhooks/${encodeURIComponent(name)}`)}
851
- onBackToList=${() => setLocation("/webhooks")}
925
+ onBackToList=${() => {
926
+ const historyStack = routeHistoryRef.current;
927
+ const hasPreviousRoute = historyStack.length > 1;
928
+ if (!hasPreviousRoute) {
929
+ setLocation("/webhooks");
930
+ return;
931
+ }
932
+ const currentPath = getHashPath();
933
+ window.history.back();
934
+ window.setTimeout(() => {
935
+ if (getHashPath() === currentPath) {
936
+ setLocation("/webhooks");
937
+ }
938
+ }, 180);
939
+ }}
852
940
  onRestartRequired=${setRestartRequired}
941
+ onOpenFile=${(relativePath) =>
942
+ navigateToBrowseFile(String(relativePath || "").trim(), { view: "edit" })}
853
943
  />
854
944
  </div>
855
945
  `;
@@ -861,7 +951,7 @@ const App = () => {
861
951
  style=${{ "--sidebar-width": `${sidebarWidthPx}px` }}
862
952
  >
863
953
  <${GlobalRestartBanner}
864
- visible=${restartRequired}
954
+ visible=${isAnyRestartRequired}
865
955
  restarting=${restartingGateway}
866
956
  onRestart=${handleGatewayRestart}
867
957
  />
@@ -879,7 +969,7 @@ const App = () => {
879
969
  onSelectNavItem=${handleSelectNavItem}
880
970
  selectedBrowsePath=${selectedBrowsePath}
881
971
  onSelectBrowseFile=${navigateToBrowseFile}
882
- onPreviewBrowseFile=${setBrowsePreviewPath}
972
+ onPreviewBrowseFile=${handleBrowsePreviewFile}
883
973
  acHasUpdate=${acHasUpdate}
884
974
  acLatest=${acLatest}
885
975
  acDismissed=${acDismissed}
@@ -921,115 +1011,121 @@ const App = () => {
921
1011
  </span>
922
1012
  </div>
923
1013
  <div class=${isBrowseRoute ? "w-full" : "max-w-2xl w-full mx-auto"}>
924
- ${isBrowseRoute
925
- ? html`
926
- <${FileViewer}
927
- filePath=${activeBrowsePath}
928
- isPreviewOnly=${Boolean(
929
- browsePreviewPath &&
930
- browsePreviewPath !== selectedBrowsePath,
931
- )}
932
- browseView=${browseViewerMode}
933
- onRequestEdit=${(targetPath) => {
934
- const normalizedTargetPath = String(targetPath || "");
935
- if (
936
- normalizedTargetPath &&
937
- normalizedTargetPath !== selectedBrowsePath
938
- ) {
939
- navigateToBrowseFile(normalizedTargetPath, { view: "edit" });
940
- return;
941
- }
942
- setLocation(buildBrowseRoute(selectedBrowsePath, { view: "edit" }));
943
- }}
1014
+ <div style=${{ display: isBrowseRoute ? "block" : "none" }}>
1015
+ <${FileViewer}
1016
+ filePath=${activeBrowsePath}
1017
+ isPreviewOnly=${false}
1018
+ browseView=${browseViewerMode}
1019
+ onRequestEdit=${(targetPath) => {
1020
+ const normalizedTargetPath = String(targetPath || "");
1021
+ if (
1022
+ normalizedTargetPath &&
1023
+ normalizedTargetPath !== selectedBrowsePath
1024
+ ) {
1025
+ navigateToBrowseFile(normalizedTargetPath, { view: "edit" });
1026
+ return;
1027
+ }
1028
+ setLocation(buildBrowseRoute(selectedBrowsePath, { view: "edit" }));
1029
+ }}
1030
+ onRequestClearSelection=${() => {
1031
+ setBrowsePreviewPath("");
1032
+ setLocation("/browse");
1033
+ }}
1034
+ />
1035
+ </div>
1036
+ <div style=${{ display: isBrowseRoute ? "none" : "block" }}>
1037
+ <div style=${{ display: location === "/general" ? "block" : "none" }}>
1038
+ <div class="pt-4">
1039
+ <${GeneralTab}
1040
+ statusData=${sharedStatus}
1041
+ watchdogData=${sharedWatchdogStatus}
1042
+ onRefreshStatuses=${refreshSharedStatuses}
1043
+ onSwitchTab=${(nextTab) => setLocation(`/${nextTab}`)}
1044
+ onNavigate=${navigateToSubScreen}
1045
+ onOpenGmailWebhook=${() => setLocation("/webhooks/gmail")}
1046
+ isActive=${location === "/general"}
1047
+ restartingGateway=${restartingGateway}
1048
+ onRestartGateway=${handleGatewayRestart}
1049
+ restartSignal=${gatewayRestartSignal}
1050
+ openclawUpdateInProgress=${openclawUpdateInProgress}
1051
+ onOpenclawVersionActionComplete=${handleOpenclawVersionActionComplete}
1052
+ onOpenclawUpdate=${handleOpenclawUpdate}
1053
+ onRestartRequired=${setRestartRequired}
944
1054
  />
945
- `
946
- : html`
947
- <${Switch}>
948
- <${Route} path="/telegram">
949
- <div class="pt-4">
950
- <${TelegramWorkspace} onBack=${exitSubScreen} />
951
- </div>
952
- </${Route}>
953
- <${Route} path="/general">
954
- <div class="pt-4">
955
- <${GeneralTab}
956
- statusData=${sharedStatus}
957
- watchdogData=${sharedWatchdogStatus}
958
- onRefreshStatuses=${refreshSharedStatuses}
959
- onSwitchTab=${(nextTab) => setLocation(`/${nextTab}`)}
960
- onNavigate=${navigateToSubScreen}
961
- isActive=${location === "/general"}
962
- restartingGateway=${restartingGateway}
963
- onRestartGateway=${handleGatewayRestart}
964
- restartSignal=${gatewayRestartSignal}
965
- openclawUpdateInProgress=${openclawUpdateInProgress}
966
- onOpenclawVersionActionComplete=${handleOpenclawVersionActionComplete}
967
- onOpenclawUpdate=${handleOpenclawUpdate}
968
- />
969
- </div>
970
- </${Route}>
971
- <${Route} path="/providers">
972
- <div class="pt-4">
973
- <${Providers} onRestartRequired=${setRestartRequired} />
974
- </div>
975
- </${Route}>
976
- <${Route} path="/watchdog">
977
- <div class="pt-4">
978
- <${WatchdogTab}
979
- gatewayStatus=${sharedStatus?.gateway || null}
980
- openclawVersion=${sharedStatus?.openclawVersion || null}
981
- watchdogStatus=${sharedWatchdogStatus}
982
- onRefreshStatuses=${refreshSharedStatuses}
983
- restartingGateway=${restartingGateway}
984
- onRestartGateway=${handleGatewayRestart}
985
- restartSignal=${gatewayRestartSignal}
986
- openclawUpdateInProgress=${openclawUpdateInProgress}
987
- onOpenclawVersionActionComplete=${handleOpenclawVersionActionComplete}
988
- onOpenclawUpdate=${handleOpenclawUpdate}
989
- />
990
- </div>
991
- </${Route}>
992
- <${Route} path="/usage/:sessionId">
993
- ${(params) => html`
1055
+ </div>
1056
+ </div>
1057
+ ${!isBrowseRoute && location !== "/general"
1058
+ ? html`
1059
+ <${Switch}>
1060
+ <${Route} path="/telegram">
1061
+ <div class="pt-4">
1062
+ <${TelegramWorkspace} onBack=${exitSubScreen} />
1063
+ </div>
1064
+ </${Route}>
1065
+ <${Route} path="/providers">
1066
+ <div class="pt-4">
1067
+ <${Providers} onRestartRequired=${setRestartRequired} />
1068
+ </div>
1069
+ </${Route}>
1070
+ <${Route} path="/watchdog">
1071
+ <div class="pt-4">
1072
+ <${WatchdogTab}
1073
+ gatewayStatus=${sharedStatus?.gateway || null}
1074
+ openclawVersion=${sharedStatus?.openclawVersion || null}
1075
+ watchdogStatus=${sharedWatchdogStatus}
1076
+ onRefreshStatuses=${refreshSharedStatuses}
1077
+ restartingGateway=${restartingGateway}
1078
+ onRestartGateway=${handleGatewayRestart}
1079
+ restartSignal=${gatewayRestartSignal}
1080
+ openclawUpdateInProgress=${openclawUpdateInProgress}
1081
+ onOpenclawVersionActionComplete=${handleOpenclawVersionActionComplete}
1082
+ onOpenclawUpdate=${handleOpenclawUpdate}
1083
+ />
1084
+ </div>
1085
+ </${Route}>
1086
+ <${Route} path="/usage/:sessionId">
1087
+ ${(params) => html`
1088
+ <div class="pt-4">
1089
+ <${UsageTab}
1090
+ sessionId=${decodeURIComponent(params.sessionId || "")}
1091
+ onSelectSession=${(id) =>
1092
+ setLocation(`/usage/${encodeURIComponent(String(id || ""))}`)}
1093
+ onBackToSessions=${() => setLocation("/usage")}
1094
+ />
1095
+ </div>
1096
+ `}
1097
+ </${Route}>
1098
+ <${Route} path="/usage">
994
1099
  <div class="pt-4">
995
1100
  <${UsageTab}
996
- sessionId=${decodeURIComponent(params.sessionId || "")}
997
1101
  onSelectSession=${(id) =>
998
1102
  setLocation(`/usage/${encodeURIComponent(String(id || ""))}`)}
999
1103
  onBackToSessions=${() => setLocation("/usage")}
1000
1104
  />
1001
1105
  </div>
1002
- `}
1003
- </${Route}>
1004
- <${Route} path="/usage">
1005
- <div class="pt-4">
1006
- <${UsageTab}
1007
- onSelectSession=${(id) =>
1008
- setLocation(`/usage/${encodeURIComponent(String(id || ""))}`)}
1009
- onBackToSessions=${() => setLocation("/usage")}
1010
- />
1011
- </div>
1012
- </${Route}>
1013
- <${Route} path="/envars">
1014
- <div class="pt-4">
1015
- <${Envars} onRestartRequired=${setRestartRequired} />
1016
- </div>
1017
- </${Route}>
1018
- <${Route} path="/webhooks/:hookName">
1019
- ${(params) =>
1020
- renderWebhooks(decodeURIComponent(params.hookName || ""))}
1021
- </${Route}>
1022
- <${Route} path="/webhooks">
1023
- ${() => renderWebhooks("")}
1024
- </${Route}>
1025
- <${Route}>
1026
- <${RouteRedirect} to="/general" />
1027
- </${Route}>
1028
- </${Switch}>
1029
- `}
1106
+ </${Route}>
1107
+ <${Route} path="/envars">
1108
+ <div class="pt-4">
1109
+ <${Envars} onRestartRequired=${setRestartRequired} />
1110
+ </div>
1111
+ </${Route}>
1112
+ <${Route} path="/webhooks/:hookName">
1113
+ ${(params) =>
1114
+ renderWebhooks(decodeURIComponent(params.hookName || ""))}
1115
+ </${Route}>
1116
+ <${Route} path="/webhooks">
1117
+ ${() => renderWebhooks("")}
1118
+ </${Route}>
1119
+ <${Route}>
1120
+ <${RouteRedirect} to="/general" />
1121
+ </${Route}>
1122
+ </${Switch}>
1123
+ `
1124
+ : null}
1125
+ </div>
1030
1126
  </div>
1031
1127
  <${ToastContainer}
1032
- className="fixed bottom-10 right-4 z-50 space-y-2 pointer-events-none"
1128
+ className="fixed top-4 right-4 z-50 space-y-2 pointer-events-none"
1033
1129
  />
1034
1130
  </div>
1035
1131