@chrysb/alphaclaw 0.8.5 → 0.8.7-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.
- package/bin/alphaclaw.js +56 -20
- package/lib/public/css/explorer.css +48 -0
- package/lib/public/css/shell.css +149 -0
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/css/theme.css +265 -0
- package/lib/public/dist/app.bundle.js +2441 -2352
- package/lib/public/js/app.js +7 -0
- package/lib/public/js/components/gateway.js +6 -3
- package/lib/public/js/components/general/index.js +2 -0
- package/lib/public/js/components/icons.js +38 -0
- package/lib/public/js/components/models-tab/provider-auth-card.js +60 -49
- package/lib/public/js/components/models-tab/use-models.js +74 -9
- package/lib/public/js/components/models.js +52 -37
- package/lib/public/js/components/onboarding/use-welcome-codex.js +34 -24
- package/lib/public/js/components/onboarding/welcome-config.js +76 -10
- package/lib/public/js/components/onboarding/welcome-form-step.js +31 -11
- package/lib/public/js/components/onboarding/welcome-header.js +12 -14
- package/lib/public/js/components/onboarding/welcome-setup-step.js +3 -3
- package/lib/public/js/components/providers.js +53 -42
- package/lib/public/js/components/routes/general-route.js +2 -0
- package/lib/public/js/components/routes/watchdog-route.js +2 -0
- package/lib/public/js/components/sidebar.js +29 -8
- package/lib/public/js/components/theme-toggle.js +113 -0
- package/lib/public/js/components/update-modal-helpers.js +12 -0
- package/lib/public/js/components/update-modal.js +2 -1
- package/lib/public/js/components/watchdog-tab/index.js +2 -0
- package/lib/public/js/components/welcome/index.js +1 -2
- package/lib/public/js/components/welcome/use-welcome.js +153 -38
- package/lib/public/js/hooks/use-app-shell-controller.js +33 -9
- package/lib/public/js/lib/api.js +35 -0
- package/lib/public/js/lib/codex-oauth-window.js +22 -0
- package/lib/public/js/lib/model-catalog.js +20 -0
- package/lib/public/js/lib/storage-keys.js +1 -1
- package/lib/public/login.html +8 -4
- package/lib/public/setup.html +9 -0
- package/lib/server/alphaclaw-version.js +30 -127
- package/lib/server/db/webhooks/index.js +48 -8
- package/lib/server/model-catalog-cache.js +251 -0
- package/lib/server/openclaw-version.js +59 -130
- package/lib/server/pending-alphaclaw-update.js +71 -0
- package/lib/server/pending-openclaw-update.js +71 -0
- package/lib/server/routes/models.js +14 -23
- package/lib/server/routes/system.js +6 -1
- package/lib/server/routes/webhooks.js +12 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { h } from "preact";
|
|
2
|
-
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
|
3
3
|
import htm from "htm";
|
|
4
4
|
import {
|
|
5
5
|
AddLineIcon,
|
|
@@ -22,6 +22,7 @@ import { OverflowMenu, OverflowMenuItem } from "./overflow-menu.js";
|
|
|
22
22
|
import { UpdateActionButton } from "./update-action-button.js";
|
|
23
23
|
import { SidebarGitPanel } from "./sidebar-git-panel.js";
|
|
24
24
|
import { UpdateModal } from "./update-modal.js";
|
|
25
|
+
import { createUpdateModalSubmitHandler } from "./update-modal-helpers.js";
|
|
25
26
|
import {
|
|
26
27
|
readUiSettings,
|
|
27
28
|
updateUiSettings,
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
getSessionDisplayLabel,
|
|
34
35
|
getSessionRowKey,
|
|
35
36
|
} from "../lib/session-keys.js";
|
|
37
|
+
import { ThemeToggle } from "./theme-toggle.js";
|
|
36
38
|
|
|
37
39
|
const html = htm.bind(h);
|
|
38
40
|
const kBrowseBottomPanelUiSettingKey = "browseBottomPanelHeightPx";
|
|
@@ -109,6 +111,7 @@ export const AppSidebar = ({
|
|
|
109
111
|
onPreviewBrowseFile = () => {},
|
|
110
112
|
acHasUpdate = false,
|
|
111
113
|
acLatest = "",
|
|
114
|
+
acRestarting = false,
|
|
112
115
|
acUpdating = false,
|
|
113
116
|
onAcUpdate = () => {},
|
|
114
117
|
agents = [],
|
|
@@ -181,6 +184,19 @@ export const AppSidebar = ({
|
|
|
181
184
|
writeUiSettings(settings);
|
|
182
185
|
}, [browseBottomPanelHeightPx]);
|
|
183
186
|
|
|
187
|
+
const handleUpdateModalClose = useCallback(() => {
|
|
188
|
+
if (acUpdating) return;
|
|
189
|
+
setUpdateModalOpen(false);
|
|
190
|
+
}, [acUpdating]);
|
|
191
|
+
|
|
192
|
+
const handleUpdateModalSubmit = useCallback(
|
|
193
|
+
createUpdateModalSubmitHandler({
|
|
194
|
+
onClose: () => setUpdateModalOpen(false),
|
|
195
|
+
onUpdate: onAcUpdate,
|
|
196
|
+
}),
|
|
197
|
+
[onAcUpdate],
|
|
198
|
+
);
|
|
199
|
+
|
|
184
200
|
const getClampedBrowseBottomPanelHeight = (value) => {
|
|
185
201
|
const layoutElement = browseLayoutRef.current;
|
|
186
202
|
if (!layoutElement) return value;
|
|
@@ -246,8 +262,14 @@ export const AppSidebar = ({
|
|
|
246
262
|
return html`
|
|
247
263
|
<div class=${`app-sidebar ${mobileSidebarOpen ? "mobile-open" : ""}`}>
|
|
248
264
|
<div class="sidebar-brand">
|
|
249
|
-
<
|
|
265
|
+
<span
|
|
266
|
+
class="ac-logo-mark"
|
|
267
|
+
style="--ac-logo-width: 20px; --ac-logo-height: 20px;"
|
|
268
|
+
aria-hidden="true"
|
|
269
|
+
></span>
|
|
250
270
|
<span><span style="color: var(--accent)">alpha</span>claw</span>
|
|
271
|
+
<span style="margin-left: auto; display: inline-flex; align-items: center; gap: 4px;">
|
|
272
|
+
<${ThemeToggle} />
|
|
251
273
|
${authEnabled && html`
|
|
252
274
|
<${OverflowMenu}
|
|
253
275
|
open=${menuOpen}
|
|
@@ -262,6 +284,7 @@ export const AppSidebar = ({
|
|
|
262
284
|
</${OverflowMenuItem}>
|
|
263
285
|
</${OverflowMenu}>
|
|
264
286
|
`}
|
|
287
|
+
</span>
|
|
265
288
|
</div>
|
|
266
289
|
<div class="sidebar-tabs">
|
|
267
290
|
<button
|
|
@@ -356,7 +379,7 @@ export const AppSidebar = ({
|
|
|
356
379
|
loading=${acUpdating}
|
|
357
380
|
warning=${true}
|
|
358
381
|
idleLabel=${`Update to v${acLatest}`}
|
|
359
|
-
loadingLabel
|
|
382
|
+
loadingLabel=${acRestarting ? "Restarting..." : "Updating..."}
|
|
360
383
|
className="w-full justify-center"
|
|
361
384
|
/>
|
|
362
385
|
`
|
|
@@ -481,13 +504,11 @@ export const AppSidebar = ({
|
|
|
481
504
|
</div>
|
|
482
505
|
<${UpdateModal}
|
|
483
506
|
visible=${updateModalOpen}
|
|
484
|
-
onClose=${
|
|
485
|
-
if (acUpdating) return;
|
|
486
|
-
setUpdateModalOpen(false);
|
|
487
|
-
}}
|
|
507
|
+
onClose=${handleUpdateModalClose}
|
|
488
508
|
version=${acLatest}
|
|
489
|
-
onUpdate=${
|
|
509
|
+
onUpdate=${handleUpdateModalSubmit}
|
|
490
510
|
updating=${acUpdating}
|
|
511
|
+
updateLoadingLabel=${acRestarting ? "Restarting..." : "Updating..."}
|
|
491
512
|
/>
|
|
492
513
|
</div>
|
|
493
514
|
`;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
3
|
+
import htm from "htm";
|
|
4
|
+
import { ComputerLineIcon, MoonIcon, SunIcon } from "./icons.js";
|
|
5
|
+
import { kThemeStorageKey } from "../lib/storage-keys.js";
|
|
6
|
+
|
|
7
|
+
const html = htm.bind(h);
|
|
8
|
+
|
|
9
|
+
const kOptions = [
|
|
10
|
+
{ id: "dark", label: "Dark", Icon: MoonIcon },
|
|
11
|
+
{ id: "light", label: "Light", Icon: SunIcon },
|
|
12
|
+
{ id: "system", label: "System", Icon: ComputerLineIcon },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/** Map a preference to the icon component shown on the trigger button. */
|
|
16
|
+
const kPrefIcon = { dark: MoonIcon, light: SunIcon, system: ComputerLineIcon };
|
|
17
|
+
|
|
18
|
+
/** Resolve a preference string to an effective "dark" | "light" value. */
|
|
19
|
+
const resolveEffective = (pref) => {
|
|
20
|
+
if (pref === "dark" || pref === "light") return pref;
|
|
21
|
+
try {
|
|
22
|
+
return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
23
|
+
} catch {
|
|
24
|
+
return "dark";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Read the stored preference. Falls back to "dark" (not OS). */
|
|
29
|
+
const readPreference = () => {
|
|
30
|
+
try {
|
|
31
|
+
const saved = localStorage.getItem(kThemeStorageKey);
|
|
32
|
+
if (saved === "dark" || saved === "light" || saved === "system") return saved;
|
|
33
|
+
} catch {}
|
|
34
|
+
return "dark";
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const applyEffective = (effective) => {
|
|
38
|
+
document.documentElement.dataset.theme = effective;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const savePreference = (pref) => {
|
|
42
|
+
try { localStorage.setItem(kThemeStorageKey, pref); } catch {}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const ThemeToggle = () => {
|
|
46
|
+
const [pref, setPref] = useState(readPreference);
|
|
47
|
+
const [open, setOpen] = useState(false);
|
|
48
|
+
const menuRef = useRef(null);
|
|
49
|
+
|
|
50
|
+
// Apply effective theme whenever preference changes (and listen for OS changes when "system").
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
applyEffective(resolveEffective(pref));
|
|
53
|
+
|
|
54
|
+
if (pref !== "system") return;
|
|
55
|
+
|
|
56
|
+
const mql = window.matchMedia("(prefers-color-scheme: light)");
|
|
57
|
+
const onChange = () => applyEffective(resolveEffective("system"));
|
|
58
|
+
mql.addEventListener("change", onChange);
|
|
59
|
+
return () => mql.removeEventListener("change", onChange);
|
|
60
|
+
}, [pref]);
|
|
61
|
+
|
|
62
|
+
// Close dropdown on outside click.
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!open) return;
|
|
65
|
+
const handler = (e) => {
|
|
66
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) setOpen(false);
|
|
67
|
+
};
|
|
68
|
+
window.addEventListener("click", handler, true);
|
|
69
|
+
return () => window.removeEventListener("click", handler, true);
|
|
70
|
+
}, [open]);
|
|
71
|
+
|
|
72
|
+
const select = (id) => {
|
|
73
|
+
setPref(id);
|
|
74
|
+
savePreference(id);
|
|
75
|
+
applyEffective(resolveEffective(id));
|
|
76
|
+
setOpen(false);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const TriggerIcon = kPrefIcon[pref] || MoonIcon;
|
|
80
|
+
|
|
81
|
+
return html`
|
|
82
|
+
<div
|
|
83
|
+
ref=${menuRef}
|
|
84
|
+
class="theme-toggle-menu"
|
|
85
|
+
>
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
onclick=${() => setOpen((o) => !o)}
|
|
89
|
+
title="Theme"
|
|
90
|
+
aria-label="Toggle theme"
|
|
91
|
+
aria-expanded=${open}
|
|
92
|
+
class="theme-toggle-trigger"
|
|
93
|
+
>
|
|
94
|
+
<${TriggerIcon} className="w-3.5 h-3.5" />
|
|
95
|
+
</button>
|
|
96
|
+
${open && html`
|
|
97
|
+
<div class="theme-toggle-dropdown">
|
|
98
|
+
${kOptions.map(({ id, label, Icon }) => html`
|
|
99
|
+
<button
|
|
100
|
+
key=${id}
|
|
101
|
+
type="button"
|
|
102
|
+
class="theme-toggle-option ${pref === id ? "active" : ""}"
|
|
103
|
+
onclick=${() => select(id)}
|
|
104
|
+
>
|
|
105
|
+
<${Icon} className="w-3.5 h-3.5" />
|
|
106
|
+
<span>${label}</span>
|
|
107
|
+
</button>
|
|
108
|
+
`)}
|
|
109
|
+
</div>
|
|
110
|
+
`}
|
|
111
|
+
</div>
|
|
112
|
+
`;
|
|
113
|
+
};
|
|
@@ -40,6 +40,7 @@ export const UpdateModal = ({
|
|
|
40
40
|
version = "",
|
|
41
41
|
onUpdate = () => {},
|
|
42
42
|
updating = false,
|
|
43
|
+
updateLoadingLabel = "Updating...",
|
|
43
44
|
}) => {
|
|
44
45
|
const requestedTag = useMemo(() => getReleaseTagFromVersion(version), [version]);
|
|
45
46
|
const [loadingNotes, setLoadingNotes] = useState(false);
|
|
@@ -163,7 +164,7 @@ export const UpdateModal = ({
|
|
|
163
164
|
onClick=${onUpdate}
|
|
164
165
|
tone="warning"
|
|
165
166
|
idleLabel=${updateLabel}
|
|
166
|
-
loadingLabel
|
|
167
|
+
loadingLabel=${updateLoadingLabel}
|
|
167
168
|
loading=${updating}
|
|
168
169
|
disabled=${loadingNotes}
|
|
169
170
|
/>
|
|
@@ -17,6 +17,7 @@ export const WatchdogTab = ({
|
|
|
17
17
|
restartingGateway = false,
|
|
18
18
|
onRestartGateway,
|
|
19
19
|
restartSignal = 0,
|
|
20
|
+
openclawRestarting = false,
|
|
20
21
|
openclawUpdateInProgress = false,
|
|
21
22
|
onOpenclawVersionActionComplete = () => {},
|
|
22
23
|
onOpenclawUpdate,
|
|
@@ -37,6 +38,7 @@ export const WatchdogTab = ({
|
|
|
37
38
|
watchdogStatus=${state.currentWatchdogStatus}
|
|
38
39
|
onRepair=${state.onRepair}
|
|
39
40
|
repairing=${state.isRepairInProgress}
|
|
41
|
+
openclawRestarting=${openclawRestarting}
|
|
40
42
|
openclawUpdateInProgress=${openclawUpdateInProgress}
|
|
41
43
|
onOpenclawVersionActionComplete=${onOpenclawVersionActionComplete}
|
|
42
44
|
onOpenclawUpdate=${onOpenclawUpdate}
|
|
@@ -102,12 +102,11 @@ export const Welcome = ({ onComplete, acVersion }) => {
|
|
|
102
102
|
error=${state.formError}
|
|
103
103
|
step=${state.step}
|
|
104
104
|
totalGroups=${kWelcomeGroups.length}
|
|
105
|
-
|
|
105
|
+
importApplied=${state.importApplied}
|
|
106
106
|
goBack=${actions.goBack}
|
|
107
107
|
goNext=${actions.goNext}
|
|
108
108
|
loading=${state.loading}
|
|
109
109
|
githubStepLoading=${state.githubStepLoading}
|
|
110
|
-
allValid=${state.allValid}
|
|
111
110
|
handleSubmit=${actions.handleSubmit}
|
|
112
111
|
/>
|
|
113
112
|
`}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
applyImport,
|
|
7
7
|
fetchModels,
|
|
8
8
|
} from "../../lib/api.js";
|
|
9
|
+
import { useCachedFetch } from "../../hooks/use-cached-fetch.js";
|
|
9
10
|
import {
|
|
10
11
|
getModelProvider,
|
|
11
12
|
getAuthProviderFromModelProvider,
|
|
@@ -13,8 +14,16 @@ import {
|
|
|
13
14
|
getVisibleAiFieldKeys,
|
|
14
15
|
kProviderAuthFields,
|
|
15
16
|
} from "../../lib/model-config.js";
|
|
17
|
+
import {
|
|
18
|
+
getInitialOnboardingModelKey,
|
|
19
|
+
getModelCatalogModels,
|
|
20
|
+
kModelCatalogCacheKey,
|
|
21
|
+
} from "../../lib/model-catalog.js";
|
|
16
22
|
import {
|
|
17
23
|
kWelcomeGroups,
|
|
24
|
+
getWelcomeGroupError,
|
|
25
|
+
findFirstInvalidWelcomeGroup,
|
|
26
|
+
normalizeGithubRepoInput,
|
|
18
27
|
isValidGithubRepoInput,
|
|
19
28
|
kGithubFlowFresh,
|
|
20
29
|
kGithubFlowImport,
|
|
@@ -40,6 +49,8 @@ export const kImportStepId = "import";
|
|
|
40
49
|
export const kSecretReviewStepId = "secret-review";
|
|
41
50
|
export const kPlaceholderReviewStepId = "placeholder-review";
|
|
42
51
|
const kImportSubstepKey = "_IMPORT_SUBSTEP";
|
|
52
|
+
const kImportAppliedKey = "_IMPORT_APPLIED";
|
|
53
|
+
const kImportedSourceRepoKey = "_IMPORTED_SOURCE_REPO";
|
|
43
54
|
const kImportPlaceholderReviewKey = "_IMPORT_PLACEHOLDER_REVIEW";
|
|
44
55
|
const kImportPlaceholderSkipConfirmedKey = "_IMPORT_PLACEHOLDER_SKIP_CONFIRMED";
|
|
45
56
|
|
|
@@ -73,14 +84,45 @@ const normalizePlaceholderReview = (review) => {
|
|
|
73
84
|
};
|
|
74
85
|
};
|
|
75
86
|
|
|
87
|
+
const normalizeTrackedRepo = (repoInput) =>
|
|
88
|
+
normalizeGithubRepoInput(repoInput).toLowerCase();
|
|
89
|
+
|
|
90
|
+
export const getImportReuseState = ({
|
|
91
|
+
githubFlow,
|
|
92
|
+
importApplied,
|
|
93
|
+
sourceRepo,
|
|
94
|
+
importedSourceRepo,
|
|
95
|
+
}) => {
|
|
96
|
+
const sourceImportAlreadyApplied =
|
|
97
|
+
githubFlow === kGithubFlowImport && !!importApplied;
|
|
98
|
+
const normalizedSourceRepo = normalizeTrackedRepo(sourceRepo);
|
|
99
|
+
const normalizedImportedSourceRepo = normalizeTrackedRepo(importedSourceRepo);
|
|
100
|
+
const sourceRepoChangedAfterImport =
|
|
101
|
+
sourceImportAlreadyApplied &&
|
|
102
|
+
!!normalizedImportedSourceRepo &&
|
|
103
|
+
normalizedSourceRepo !== normalizedImportedSourceRepo;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
sourceImportAlreadyApplied,
|
|
107
|
+
sourceRepoChangedAfterImport,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
76
111
|
export const useWelcome = ({ onComplete }) => {
|
|
77
112
|
const kSetupStepIndex = kWelcomeGroups.length;
|
|
78
113
|
const kPairingStepIndex = kSetupStepIndex + 1;
|
|
79
|
-
const {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
114
|
+
const {
|
|
115
|
+
vals,
|
|
116
|
+
setVals,
|
|
117
|
+
setValue: setStoredValue,
|
|
118
|
+
step,
|
|
119
|
+
setStep,
|
|
120
|
+
setupError,
|
|
121
|
+
setSetupError,
|
|
122
|
+
} = useWelcomeStorage({
|
|
123
|
+
kSetupStepIndex,
|
|
124
|
+
kPairingStepIndex,
|
|
125
|
+
});
|
|
84
126
|
const [models, setModels] = useState([]);
|
|
85
127
|
const [modelsLoading, setModelsLoading] = useState(true);
|
|
86
128
|
const [modelsError, setModelsError] = useState(null);
|
|
@@ -110,6 +152,14 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
110
152
|
const [importScanResult, setImportScanResult] = useState(null);
|
|
111
153
|
const [importScanning, setImportScanning] = useState(false);
|
|
112
154
|
const [importError, setImportError] = useState(null);
|
|
155
|
+
const modelsFetchState = useCachedFetch(kModelCatalogCacheKey, fetchModels, {
|
|
156
|
+
maxAgeMs: 30000,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const setValue = (key, value) => {
|
|
160
|
+
if (formError) setFormError(null);
|
|
161
|
+
setStoredValue(key, value);
|
|
162
|
+
};
|
|
113
163
|
|
|
114
164
|
const setImportStep = (nextStep) => {
|
|
115
165
|
setImportStepState(nextStep);
|
|
@@ -129,24 +179,58 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
129
179
|
};
|
|
130
180
|
|
|
131
181
|
useEffect(() => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
182
|
+
const list = getModelCatalogModels(modelsFetchState.data);
|
|
183
|
+
if (!modelsFetchState.data) return;
|
|
184
|
+
setModels(list);
|
|
185
|
+
setModelsError(list.length > 0 ? null : "No models found");
|
|
186
|
+
const defaultModelKey = getInitialOnboardingModelKey({
|
|
187
|
+
catalog: list,
|
|
188
|
+
currentModelKey: vals.MODEL_KEY,
|
|
189
|
+
});
|
|
190
|
+
if (!vals.MODEL_KEY && defaultModelKey) {
|
|
191
|
+
setVals((prev) => ({ ...prev, MODEL_KEY: defaultModelKey }));
|
|
192
|
+
}
|
|
193
|
+
}, [modelsFetchState.data, setVals, vals.MODEL_KEY]);
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
const hasModels = getModelCatalogModels(modelsFetchState.data).length > 0;
|
|
197
|
+
setModelsLoading(modelsFetchState.loading && !hasModels);
|
|
198
|
+
}, [modelsFetchState.data, modelsFetchState.loading]);
|
|
145
199
|
|
|
146
|
-
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (!modelsFetchState.error) return;
|
|
202
|
+
setModelsError("Failed to load models");
|
|
203
|
+
setModelsLoading(false);
|
|
204
|
+
}, [modelsFetchState.error]);
|
|
205
|
+
|
|
206
|
+
const getValidationContext = (currentVals = {}) => {
|
|
207
|
+
const currentSelectedProvider = getModelProvider(
|
|
208
|
+
String(currentVals.MODEL_KEY || "").trim(),
|
|
209
|
+
);
|
|
210
|
+
const currentSelectedAuthProvider =
|
|
211
|
+
getAuthProviderFromModelProvider(currentSelectedProvider);
|
|
212
|
+
const currentProviderAuthFields =
|
|
213
|
+
kProviderAuthFields[currentSelectedAuthProvider] || [];
|
|
214
|
+
const currentHasAi =
|
|
215
|
+
currentSelectedProvider === "openai-codex"
|
|
216
|
+
? !!codexStatus.connected
|
|
217
|
+
: currentProviderAuthFields.some((field) =>
|
|
218
|
+
!!String(currentVals[field.key] || "").trim(),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
hasAi: currentHasAi,
|
|
223
|
+
selectedProvider: currentSelectedProvider,
|
|
224
|
+
codexLoading,
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const validationContext = getValidationContext(vals);
|
|
229
|
+
const { selectedProvider, hasAi } = validationContext;
|
|
147
230
|
const placeholderReview = normalizePlaceholderReview(
|
|
148
231
|
vals[kImportPlaceholderReviewKey],
|
|
149
232
|
);
|
|
233
|
+
const importApplied = !!vals[kImportAppliedKey];
|
|
150
234
|
const featuredModels = getFeaturedModels(models);
|
|
151
235
|
const baseModelOptions = showAllModels
|
|
152
236
|
? models
|
|
@@ -164,23 +248,10 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
164
248
|
const canToggleFullCatalog =
|
|
165
249
|
featuredModels.length > 0 && models.length > featuredModels.length;
|
|
166
250
|
const visibleAiFieldKeys = getVisibleAiFieldKeys(selectedProvider);
|
|
167
|
-
const selectedAuthProvider = getAuthProviderFromModelProvider(selectedProvider);
|
|
168
|
-
const selectedProviderAuthFields = kProviderAuthFields[selectedAuthProvider] || [];
|
|
169
|
-
const hasAi =
|
|
170
|
-
selectedProvider === "openai-codex"
|
|
171
|
-
? !!codexStatus.connected
|
|
172
|
-
: selectedProviderAuthFields.some(
|
|
173
|
-
(field) => !!String(vals[field.key] || "").trim(),
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
const allValid = kWelcomeGroups.every((group) => group.validate(vals, { hasAi }));
|
|
177
251
|
const isPreStep = step === -1;
|
|
178
252
|
const isSetupStep = step === kSetupStepIndex;
|
|
179
253
|
const isPairingStep = step === kPairingStepIndex;
|
|
180
254
|
const activeGroup = step >= 0 && step < kSetupStepIndex ? kWelcomeGroups[step] : null;
|
|
181
|
-
const currentGroupValid = activeGroup
|
|
182
|
-
? activeGroup.validate(vals, { hasAi })
|
|
183
|
-
: false;
|
|
184
255
|
const selectedPairingChannel = String(
|
|
185
256
|
vals[kPairingChannelKey] || getPreferredPairingChannel(vals),
|
|
186
257
|
);
|
|
@@ -202,7 +273,21 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
202
273
|
const handleSubmit = async () => {
|
|
203
274
|
const { normalizedVals, didChange } = normalizeOnboardingVals(vals);
|
|
204
275
|
if (didChange) setVals(normalizedVals);
|
|
205
|
-
|
|
276
|
+
const submitValidationContext = getValidationContext(normalizedVals);
|
|
277
|
+
const invalidGroup = findFirstInvalidWelcomeGroup(
|
|
278
|
+
normalizedVals,
|
|
279
|
+
submitValidationContext,
|
|
280
|
+
);
|
|
281
|
+
if (invalidGroup) {
|
|
282
|
+
setFormError(
|
|
283
|
+
getWelcomeGroupError(
|
|
284
|
+
invalidGroup.id,
|
|
285
|
+
normalizedVals,
|
|
286
|
+
submitValidationContext,
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
setSetupError(null);
|
|
290
|
+
setStep(kWelcomeGroups.findIndex((group) => group.id === invalidGroup.id));
|
|
206
291
|
return;
|
|
207
292
|
}
|
|
208
293
|
if (loading) return;
|
|
@@ -309,7 +394,17 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
309
394
|
const goNext = async () => {
|
|
310
395
|
const { normalizedVals, didChange } = normalizeOnboardingVals(vals);
|
|
311
396
|
if (didChange) setVals(normalizedVals);
|
|
312
|
-
if (!activeGroup
|
|
397
|
+
if (!activeGroup) return;
|
|
398
|
+
const stepValidationContext = getValidationContext(normalizedVals);
|
|
399
|
+
const stepValidationError = getWelcomeGroupError(
|
|
400
|
+
activeGroup.id,
|
|
401
|
+
normalizedVals,
|
|
402
|
+
stepValidationContext,
|
|
403
|
+
);
|
|
404
|
+
if (stepValidationError) {
|
|
405
|
+
setFormError(stepValidationError);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
313
408
|
setFormError(null);
|
|
314
409
|
if (activeGroup.id === "github") {
|
|
315
410
|
const githubFlow = normalizedVals._GITHUB_FLOW || kGithubFlowFresh;
|
|
@@ -318,6 +413,19 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
318
413
|
? kGithubTargetRepoModeCreate
|
|
319
414
|
: normalizedVals._GITHUB_TARGET_REPO_MODE ||
|
|
320
415
|
kGithubTargetRepoModeCreate;
|
|
416
|
+
const { sourceImportAlreadyApplied, sourceRepoChangedAfterImport } =
|
|
417
|
+
getImportReuseState({
|
|
418
|
+
githubFlow,
|
|
419
|
+
importApplied: normalizedVals[kImportAppliedKey],
|
|
420
|
+
sourceRepo: normalizedVals._GITHUB_SOURCE_REPO,
|
|
421
|
+
importedSourceRepo: normalizedVals[kImportedSourceRepoKey],
|
|
422
|
+
});
|
|
423
|
+
if (sourceRepoChangedAfterImport) {
|
|
424
|
+
setFormError(
|
|
425
|
+
"The source repo has already been imported into this setup. You can still change the target repo, but changing the source repo requires restarting onboarding.",
|
|
426
|
+
);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
321
429
|
const targetVerifyMode =
|
|
322
430
|
targetRepoMode === kGithubTargetRepoModeExistingEmpty
|
|
323
431
|
? kRepoModeExisting
|
|
@@ -327,9 +435,11 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
327
435
|
? normalizedVals._GITHUB_SOURCE_REPO
|
|
328
436
|
: normalizedVals.GITHUB_WORKSPACE_REPO;
|
|
329
437
|
setGithubStepLoading(true);
|
|
330
|
-
|
|
438
|
+
if (!sourceImportAlreadyApplied) {
|
|
439
|
+
clearPlaceholderReview();
|
|
440
|
+
}
|
|
331
441
|
try {
|
|
332
|
-
if (githubFlow === kGithubFlowImport) {
|
|
442
|
+
if (githubFlow === kGithubFlowImport && !sourceImportAlreadyApplied) {
|
|
333
443
|
const sourceResult = await verifyGithubOnboardingRepo(
|
|
334
444
|
sourceRepo,
|
|
335
445
|
normalizedVals.GITHUB_TOKEN,
|
|
@@ -451,10 +561,16 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
451
561
|
const nextPlaceholderReview = normalizePlaceholderReview(
|
|
452
562
|
result.placeholderReview,
|
|
453
563
|
);
|
|
564
|
+
const importedSourceRepo =
|
|
565
|
+
vals._GITHUB_FLOW === kGithubFlowImport
|
|
566
|
+
? normalizeGithubRepoInput(vals._GITHUB_SOURCE_REPO)
|
|
567
|
+
: "";
|
|
454
568
|
setVals((prev) => ({
|
|
455
569
|
...prev,
|
|
456
570
|
...approvedImportVals,
|
|
457
571
|
...(result.preFill || {}),
|
|
572
|
+
[kImportAppliedKey]: true,
|
|
573
|
+
[kImportedSourceRepoKey]: importedSourceRepo,
|
|
458
574
|
[kImportPlaceholderReviewKey]: nextPlaceholderReview,
|
|
459
575
|
[kImportPlaceholderSkipConfirmedKey]: false,
|
|
460
576
|
}));
|
|
@@ -540,17 +656,16 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
540
656
|
importScanResult,
|
|
541
657
|
importScanning,
|
|
542
658
|
importError,
|
|
659
|
+
importApplied,
|
|
543
660
|
selectedProvider,
|
|
544
661
|
modelOptions,
|
|
545
662
|
canToggleFullCatalog,
|
|
546
663
|
visibleAiFieldKeys,
|
|
547
664
|
hasAi,
|
|
548
|
-
allValid,
|
|
549
665
|
isPreStep,
|
|
550
666
|
isSetupStep,
|
|
551
667
|
isPairingStep,
|
|
552
668
|
activeGroup,
|
|
553
|
-
currentGroupValid,
|
|
554
669
|
selectedPairingChannel,
|
|
555
670
|
placeholderReview,
|
|
556
671
|
isImportStep,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
fetchAuthStatus,
|
|
6
6
|
fetchAlphaclawVersion,
|
|
7
7
|
updateAlphaclaw,
|
|
8
|
+
waitForAlphaclawRestart,
|
|
8
9
|
fetchRestartStatus,
|
|
9
10
|
dismissRestartStatus,
|
|
10
11
|
restartGateway,
|
|
@@ -25,6 +26,7 @@ export const useAppShellController = ({ location = "" } = {}) => {
|
|
|
25
26
|
const [acLatest, setAcLatest] = useState(null);
|
|
26
27
|
const [acHasUpdate, setAcHasUpdate] = useState(false);
|
|
27
28
|
const [acUpdating, setAcUpdating] = useState(false);
|
|
29
|
+
const [acRestarting, setAcRestarting] = useState(false);
|
|
28
30
|
const [restartRequired, setRestartRequired] = useState(false);
|
|
29
31
|
const [browseRestartRequired, setBrowseRestartRequired] = useState(false);
|
|
30
32
|
const [restartingGateway, setRestartingGateway] = useState(false);
|
|
@@ -32,6 +34,7 @@ export const useAppShellController = ({ location = "" } = {}) => {
|
|
|
32
34
|
const [statusPollCadenceMs, setStatusPollCadenceMs] = useState(15000);
|
|
33
35
|
const [statusPollingGraceElapsed, setStatusPollingGraceElapsed] = useState(false);
|
|
34
36
|
const [openclawUpdateInProgress, setOpenclawUpdateInProgress] = useState(false);
|
|
37
|
+
const [openclawRestarting, setOpenclawRestarting] = useState(false);
|
|
35
38
|
const [statusStreamConnected, setStatusStreamConnected] = useState(false);
|
|
36
39
|
const [statusStreamStatus, setStatusStreamStatus] = useState(null);
|
|
37
40
|
const [statusStreamWatchdog, setStatusStreamWatchdog] = useState(null);
|
|
@@ -241,17 +244,25 @@ export const useAppShellController = ({ location = "" } = {}) => {
|
|
|
241
244
|
return { ok: false, error: "OpenClaw update already in progress" };
|
|
242
245
|
}
|
|
243
246
|
setOpenclawUpdateInProgress(true);
|
|
247
|
+
setOpenclawRestarting(false);
|
|
244
248
|
try {
|
|
245
249
|
const data = await updateOpenclaw();
|
|
250
|
+
if (data?.ok && data?.restarting) {
|
|
251
|
+
setOpenclawRestarting(true);
|
|
252
|
+
await waitForAlphaclawRestart();
|
|
253
|
+
window.location.reload();
|
|
254
|
+
return { ...data, restartHandled: true };
|
|
255
|
+
}
|
|
256
|
+
setOpenclawUpdateInProgress(false);
|
|
257
|
+
setOpenclawRestarting(false);
|
|
246
258
|
return data;
|
|
247
|
-
}
|
|
259
|
+
} catch (err) {
|
|
260
|
+
const message = err.message || "Could not update OpenClaw";
|
|
248
261
|
setOpenclawUpdateInProgress(false);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
setTimeout(refreshSharedStatuses, 3500);
|
|
252
|
-
setTimeout(refreshRestartStatus, 1200);
|
|
262
|
+
setOpenclawRestarting(false);
|
|
263
|
+
return { ok: false, error: message };
|
|
253
264
|
}
|
|
254
|
-
}, [openclawUpdateInProgress
|
|
265
|
+
}, [openclawUpdateInProgress]);
|
|
255
266
|
|
|
256
267
|
const handleOpenclawVersionActionComplete = useCallback(
|
|
257
268
|
({ type }) => {
|
|
@@ -263,20 +274,31 @@ export const useAppShellController = ({ location = "" } = {}) => {
|
|
|
263
274
|
);
|
|
264
275
|
|
|
265
276
|
const handleAcUpdate = useCallback(async () => {
|
|
266
|
-
if (acUpdating)
|
|
277
|
+
if (acUpdating) {
|
|
278
|
+
return { ok: false, error: "AlphaClaw update already in progress" };
|
|
279
|
+
}
|
|
267
280
|
setAcUpdating(true);
|
|
281
|
+
setAcRestarting(false);
|
|
268
282
|
try {
|
|
269
283
|
const data = await updateAlphaclaw();
|
|
270
284
|
if (data.ok) {
|
|
271
285
|
showToast("AlphaClaw updated — restarting...", "success");
|
|
272
|
-
|
|
286
|
+
setAcRestarting(true);
|
|
287
|
+
await waitForAlphaclawRestart();
|
|
288
|
+
window.location.reload();
|
|
289
|
+
return data;
|
|
273
290
|
} else {
|
|
274
291
|
showToast(data.error || "AlphaClaw update failed", "error");
|
|
275
292
|
setAcUpdating(false);
|
|
293
|
+
setAcRestarting(false);
|
|
294
|
+
return data;
|
|
276
295
|
}
|
|
277
296
|
} catch (err) {
|
|
278
|
-
|
|
297
|
+
const message = err.message || "Could not update AlphaClaw";
|
|
298
|
+
showToast(message, "error");
|
|
279
299
|
setAcUpdating(false);
|
|
300
|
+
setAcRestarting(false);
|
|
301
|
+
return { ok: false, error: message };
|
|
280
302
|
}
|
|
281
303
|
}, [acUpdating]);
|
|
282
304
|
|
|
@@ -296,12 +318,14 @@ export const useAppShellController = ({ location = "" } = {}) => {
|
|
|
296
318
|
state: {
|
|
297
319
|
acHasUpdate,
|
|
298
320
|
acLatest,
|
|
321
|
+
acRestarting,
|
|
299
322
|
acUpdating,
|
|
300
323
|
acVersion,
|
|
301
324
|
authEnabled,
|
|
302
325
|
gatewayRestartSignal,
|
|
303
326
|
isAnyRestartRequired,
|
|
304
327
|
onboarded,
|
|
328
|
+
openclawRestarting,
|
|
305
329
|
openclawUpdateInProgress,
|
|
306
330
|
restartingGateway,
|
|
307
331
|
sharedDoctorStatus,
|