@chrysb/alphaclaw 0.1.14 → 0.1.16
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/theme.css +1 -0
- package/lib/public/js/app.js +13 -9
- package/lib/public/js/components/credentials-modal.js +17 -18
- package/lib/public/js/components/envars.js +12 -22
- package/lib/public/js/components/onboarding/welcome-config.js +108 -0
- package/lib/public/js/components/onboarding/welcome-form-step.js +283 -0
- package/lib/public/js/components/onboarding/welcome-header.js +57 -0
- package/lib/public/js/components/onboarding/welcome-setup-step.js +45 -0
- package/lib/public/js/components/scope-picker.js +1 -1
- package/lib/public/js/components/secret-input.js +45 -0
- package/lib/public/js/components/welcome.js +102 -331
- package/lib/public/login.html +12 -4
- package/package.json +1 -1
|
@@ -0,0 +1,57 @@
|
|
|
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 WelcomeHeader = ({
|
|
7
|
+
groups,
|
|
8
|
+
step,
|
|
9
|
+
isSetupStep,
|
|
10
|
+
stepNumber,
|
|
11
|
+
activeStepLabel,
|
|
12
|
+
vals,
|
|
13
|
+
hasAi,
|
|
14
|
+
}) => {
|
|
15
|
+
const progressSteps = [...groups, { id: "setup", title: "Initializing" }];
|
|
16
|
+
|
|
17
|
+
return html`
|
|
18
|
+
<div class="text-center mb-1">
|
|
19
|
+
<img
|
|
20
|
+
src="./img/logo.svg"
|
|
21
|
+
alt="alphaclaw"
|
|
22
|
+
class="mx-auto mb-3"
|
|
23
|
+
width="32"
|
|
24
|
+
height="33"
|
|
25
|
+
/>
|
|
26
|
+
<h1 class="text-2xl font-semibold mb-2">Setup</h1>
|
|
27
|
+
<p style="color: var(--text-muted)" class="text-sm">
|
|
28
|
+
Let's get your agent running
|
|
29
|
+
</p>
|
|
30
|
+
<p class="text-xs my-2" style="color: var(--text-dim)">
|
|
31
|
+
Step ${stepNumber} of ${progressSteps.length} - ${activeStepLabel}
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="flex items-center gap-2">
|
|
36
|
+
${progressSteps.map((group, idx) => {
|
|
37
|
+
const isFinalStep = idx === progressSteps.length - 1;
|
|
38
|
+
const isActive = idx === step;
|
|
39
|
+
const isComplete = isFinalStep
|
|
40
|
+
? isSetupStep
|
|
41
|
+
: idx < step && group.validate(vals, { hasAi });
|
|
42
|
+
const bg = isActive
|
|
43
|
+
? "rgba(99, 235, 255, 0.9)"
|
|
44
|
+
: isComplete
|
|
45
|
+
? "rgba(99, 235, 255, 0.55)"
|
|
46
|
+
: "rgba(82, 94, 122, 0.45)";
|
|
47
|
+
return html`
|
|
48
|
+
<div
|
|
49
|
+
class="h-1 flex-1 rounded-full transition-colors"
|
|
50
|
+
style=${{ background: bg }}
|
|
51
|
+
title=${group.title}
|
|
52
|
+
></div>
|
|
53
|
+
`;
|
|
54
|
+
})}
|
|
55
|
+
</div>
|
|
56
|
+
`;
|
|
57
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
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 WelcomeSetupStep = ({ error, loading, onRetry }) => html`
|
|
7
|
+
<div class="py-10 flex flex-col items-center text-center gap-4">
|
|
8
|
+
<svg
|
|
9
|
+
class="animate-spin h-8 w-8 text-white"
|
|
10
|
+
viewBox="0 0 24 24"
|
|
11
|
+
fill="none"
|
|
12
|
+
>
|
|
13
|
+
<circle
|
|
14
|
+
class="opacity-25"
|
|
15
|
+
cx="12"
|
|
16
|
+
cy="12"
|
|
17
|
+
r="10"
|
|
18
|
+
stroke="currentColor"
|
|
19
|
+
stroke-width="4"
|
|
20
|
+
/>
|
|
21
|
+
<path
|
|
22
|
+
class="opacity-75"
|
|
23
|
+
fill="currentColor"
|
|
24
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
25
|
+
/>
|
|
26
|
+
</svg>
|
|
27
|
+
<h3 class="text-lg font-semibold text-white">Initializing OpenClaw...</h3>
|
|
28
|
+
<p class="text-sm text-gray-500">This could take 10-15 seconds</p>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
${error
|
|
32
|
+
? html`<div class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm">
|
|
33
|
+
${error}
|
|
34
|
+
</div>
|
|
35
|
+
<button
|
|
36
|
+
onclick=${onRetry}
|
|
37
|
+
disabled=${loading}
|
|
38
|
+
class="w-full text-sm font-medium px-4 py-3 rounded-xl transition-all ${loading
|
|
39
|
+
? "bg-gray-800 text-gray-500 cursor-not-allowed"
|
|
40
|
+
: "bg-white text-black hover:opacity-85"}"
|
|
41
|
+
>
|
|
42
|
+
${loading ? "Retrying..." : "Retry"}
|
|
43
|
+
</button>`
|
|
44
|
+
: null}
|
|
45
|
+
`;
|
|
@@ -46,7 +46,7 @@ export function ScopePicker({ scopes, onToggle, apiStatus, loading }) {
|
|
|
46
46
|
apiIndicator = html`<span class="text-gray-500 text-xs flex items-center gap-1"><span class="inline-block w-3 h-3 border-2 border-gray-500 border-t-transparent rounded-full animate-spin"></span></span>`;
|
|
47
47
|
} else if (api) {
|
|
48
48
|
if (api.status === 'ok') {
|
|
49
|
-
apiIndicator = html`<a href=${api.enableUrl || getApiEnableUrl(s.key)} target="_blank" class="text-green-500 hover:text-green-300 text-xs"
|
|
49
|
+
apiIndicator = html`<a href=${api.enableUrl || getApiEnableUrl(s.key)} target="_blank" class="text-green-500 hover:text-green-300 text-xs px-1.5 py-0.5 rounded bg-green-500/10">API ✓</a>`;
|
|
50
50
|
} else if (api.status === 'not_enabled') {
|
|
51
51
|
apiIndicator = html`<a href=${api.enableUrl} target="_blank" class="text-red-400 hover:text-red-300 text-xs underline">Enable API</a>`;
|
|
52
52
|
} else if (api.status === 'error') {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import { useState } from "https://esm.sh/preact/hooks";
|
|
3
|
+
import htm from "https://esm.sh/htm";
|
|
4
|
+
const html = htm.bind(h);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reusable input with show/hide toggle for secret values.
|
|
8
|
+
*
|
|
9
|
+
* Props:
|
|
10
|
+
* value, onInput, placeholder, inputClass, disabled
|
|
11
|
+
* isSecret – treat as password field (default true)
|
|
12
|
+
*/
|
|
13
|
+
export const SecretInput = ({
|
|
14
|
+
value = "",
|
|
15
|
+
onInput,
|
|
16
|
+
placeholder = "",
|
|
17
|
+
inputClass = "",
|
|
18
|
+
disabled = false,
|
|
19
|
+
isSecret = true,
|
|
20
|
+
}) => {
|
|
21
|
+
const [visible, setVisible] = useState(false);
|
|
22
|
+
const showToggle = isSecret;
|
|
23
|
+
|
|
24
|
+
return html`
|
|
25
|
+
<div class="flex-1 min-w-0 flex items-center gap-1">
|
|
26
|
+
<input
|
|
27
|
+
type=${isSecret && !visible ? "password" : "text"}
|
|
28
|
+
value=${value}
|
|
29
|
+
placeholder=${placeholder}
|
|
30
|
+
onInput=${onInput}
|
|
31
|
+
disabled=${disabled}
|
|
32
|
+
class=${inputClass}
|
|
33
|
+
/>
|
|
34
|
+
${showToggle
|
|
35
|
+
? html`<button
|
|
36
|
+
type="button"
|
|
37
|
+
onclick=${() => setVisible((v) => !v)}
|
|
38
|
+
class="text-gray-500 hover:text-gray-300 px-1 text-xs shrink-0"
|
|
39
|
+
>
|
|
40
|
+
${visible ? "Hide" : "Show"}
|
|
41
|
+
</button>`
|
|
42
|
+
: null}
|
|
43
|
+
</div>
|
|
44
|
+
`;
|
|
45
|
+
};
|
|
@@ -12,113 +12,24 @@ import {
|
|
|
12
12
|
getModelProvider,
|
|
13
13
|
getFeaturedModels,
|
|
14
14
|
getVisibleAiFieldKeys,
|
|
15
|
-
kAllAiAuthFields,
|
|
16
15
|
} from "../lib/model-config.js";
|
|
16
|
+
import { kWelcomeGroups } from "./onboarding/welcome-config.js";
|
|
17
|
+
import { WelcomeHeader } from "./onboarding/welcome-header.js";
|
|
18
|
+
import { WelcomeSetupStep } from "./onboarding/welcome-setup-step.js";
|
|
19
|
+
import { WelcomeFormStep } from "./onboarding/welcome-form-step.js";
|
|
17
20
|
const html = htm.bind(h);
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
{
|
|
21
|
-
id: "ai",
|
|
22
|
-
title: "Primary Agent Model",
|
|
23
|
-
description: "Choose your main model and authenticate its provider",
|
|
24
|
-
fields: kAllAiAuthFields,
|
|
25
|
-
validate: (vals, ctx = {}) => !!(vals.MODEL_KEY && ctx.hasAi),
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: "github",
|
|
29
|
-
title: "GitHub",
|
|
30
|
-
description: "Backs up your agent's config and workspace",
|
|
31
|
-
fields: [
|
|
32
|
-
{
|
|
33
|
-
key: "GITHUB_TOKEN",
|
|
34
|
-
label: "Personal Access Token",
|
|
35
|
-
hint: html`Create a classic PAT at${" "}<a
|
|
36
|
-
href="https://github.com/settings/tokens"
|
|
37
|
-
target="_blank"
|
|
38
|
-
class="hover:underline" style="color: var(--accent)"
|
|
39
|
-
>github.com/settings/tokens</a
|
|
40
|
-
>${" "}with${" "}<code class="text-xs bg-black/30 px-1 rounded">repo</code>${" "}scope`,
|
|
41
|
-
placeholder: "ghp_...",
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
key: "GITHUB_WORKSPACE_REPO",
|
|
45
|
-
label: "Workspace Repo",
|
|
46
|
-
hint: "A new private repo will be created for you",
|
|
47
|
-
placeholder: "username/my-agent",
|
|
48
|
-
isText: true,
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
validate: (vals) => !!(vals.GITHUB_TOKEN && vals.GITHUB_WORKSPACE_REPO),
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: "channels",
|
|
55
|
-
title: "Channels",
|
|
56
|
-
description: "At least one is required to talk to your agent",
|
|
57
|
-
fields: [
|
|
58
|
-
{
|
|
59
|
-
key: "TELEGRAM_BOT_TOKEN",
|
|
60
|
-
label: "Telegram Bot Token",
|
|
61
|
-
hint: html`From${" "}<a
|
|
62
|
-
href="https://t.me/BotFather"
|
|
63
|
-
target="_blank"
|
|
64
|
-
class="hover:underline" style="color: var(--accent)"
|
|
65
|
-
>@BotFather</a
|
|
66
|
-
>${" "}·${" "}<a
|
|
67
|
-
href="https://docs.openclaw.ai/channels/telegram"
|
|
68
|
-
target="_blank"
|
|
69
|
-
class="hover:underline" style="color: var(--accent)"
|
|
70
|
-
>full guide</a
|
|
71
|
-
>`,
|
|
72
|
-
placeholder: "123456789:AAH...",
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
key: "DISCORD_BOT_TOKEN",
|
|
76
|
-
label: "Discord Bot Token",
|
|
77
|
-
hint: html`From${" "}<a
|
|
78
|
-
href="https://discord.com/developers/applications"
|
|
79
|
-
target="_blank"
|
|
80
|
-
class="hover:underline" style="color: var(--accent)"
|
|
81
|
-
>Developer Portal</a
|
|
82
|
-
>${" "}·${" "}<a
|
|
83
|
-
href="https://docs.openclaw.ai/channels/discord"
|
|
84
|
-
target="_blank"
|
|
85
|
-
class="hover:underline" style="color: var(--accent)"
|
|
86
|
-
>full guide</a
|
|
87
|
-
>`,
|
|
88
|
-
placeholder: "MTQ3...",
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
validate: (vals) => !!(vals.TELEGRAM_BOT_TOKEN || vals.DISCORD_BOT_TOKEN),
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
id: "tools",
|
|
95
|
-
title: "Tools (optional)",
|
|
96
|
-
description: "Enable extra capabilities for your agent",
|
|
97
|
-
fields: [
|
|
98
|
-
{
|
|
99
|
-
key: "BRAVE_API_KEY",
|
|
100
|
-
label: "Brave Search API Key",
|
|
101
|
-
hint: html`From${" "}<a
|
|
102
|
-
href="https://brave.com/search/api/"
|
|
103
|
-
target="_blank"
|
|
104
|
-
class="hover:underline" style="color: var(--accent)"
|
|
105
|
-
>brave.com/search/api</a
|
|
106
|
-
>${" "}-${" "}free tier available`,
|
|
107
|
-
placeholder: "BSA...",
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
validate: () => true,
|
|
111
|
-
},
|
|
112
|
-
];
|
|
21
|
+
const kOnboardingStorageKey = "openclaw_setup";
|
|
22
|
+
const kOnboardingStepKey = "_step";
|
|
113
23
|
|
|
114
24
|
export const Welcome = ({ onComplete }) => {
|
|
115
|
-
const [
|
|
25
|
+
const [initialSetupState] = useState(() => {
|
|
116
26
|
try {
|
|
117
|
-
return JSON.parse(localStorage.getItem(
|
|
27
|
+
return JSON.parse(localStorage.getItem(kOnboardingStorageKey) || "{}");
|
|
118
28
|
} catch {
|
|
119
29
|
return {};
|
|
120
30
|
}
|
|
121
31
|
});
|
|
32
|
+
const [vals, setVals] = useState(() => ({ ...initialSetupState }));
|
|
122
33
|
const [models, setModels] = useState([]);
|
|
123
34
|
const [modelsLoading, setModelsLoading] = useState(true);
|
|
124
35
|
const [modelsError, setModelsError] = useState(null);
|
|
@@ -133,10 +44,6 @@ export const Welcome = ({ onComplete }) => {
|
|
|
133
44
|
const [error, setError] = useState(null);
|
|
134
45
|
const codexPopupPollRef = useRef(null);
|
|
135
46
|
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
localStorage.setItem("openclaw_setup", JSON.stringify(vals));
|
|
138
|
-
}, [vals]);
|
|
139
|
-
|
|
140
47
|
useEffect(() => {
|
|
141
48
|
fetchModels()
|
|
142
49
|
.then((result) => {
|
|
@@ -225,7 +132,31 @@ export const Welcome = ({ onComplete }) => {
|
|
|
225
132
|
? !!(codexStatus.connected || vals.OPENAI_API_KEY)
|
|
226
133
|
: false;
|
|
227
134
|
|
|
228
|
-
const allValid =
|
|
135
|
+
const allValid = kWelcomeGroups.every((g) => g.validate(vals, { hasAi }));
|
|
136
|
+
const kFinalSetupStep = kWelcomeGroups.length;
|
|
137
|
+
const [step, setStep] = useState(() => {
|
|
138
|
+
const parsedStep = Number.parseInt(
|
|
139
|
+
String(initialSetupState?.[kOnboardingStepKey] || ""),
|
|
140
|
+
10,
|
|
141
|
+
);
|
|
142
|
+
if (!Number.isFinite(parsedStep)) return 0;
|
|
143
|
+
return Math.max(0, Math.min(kFinalSetupStep, parsedStep));
|
|
144
|
+
});
|
|
145
|
+
const isSetupStep = step === kFinalSetupStep;
|
|
146
|
+
const activeGroup = !isSetupStep ? kWelcomeGroups[step] : null;
|
|
147
|
+
const currentGroupValid = activeGroup
|
|
148
|
+
? activeGroup.validate(vals, { hasAi })
|
|
149
|
+
: false;
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
localStorage.setItem(
|
|
153
|
+
kOnboardingStorageKey,
|
|
154
|
+
JSON.stringify({
|
|
155
|
+
...vals,
|
|
156
|
+
[kOnboardingStepKey]: step,
|
|
157
|
+
}),
|
|
158
|
+
);
|
|
159
|
+
}, [vals, step]);
|
|
229
160
|
|
|
230
161
|
const startCodexAuth = () => {
|
|
231
162
|
if (codexStatus.connected) return;
|
|
@@ -287,6 +218,7 @@ export const Welcome = ({ onComplete }) => {
|
|
|
287
218
|
|
|
288
219
|
const handleSubmit = async () => {
|
|
289
220
|
if (!allValid || loading) return;
|
|
221
|
+
setStep(kFinalSetupStep);
|
|
290
222
|
setLoading(true);
|
|
291
223
|
setError(null);
|
|
292
224
|
|
|
@@ -297,7 +229,7 @@ export const Welcome = ({ onComplete }) => {
|
|
|
297
229
|
.map(([key, value]) => ({ key, value }));
|
|
298
230
|
const result = await runOnboard(vars, vals.MODEL_KEY);
|
|
299
231
|
if (!result.ok) throw new Error(result.error || "Onboarding failed");
|
|
300
|
-
localStorage.removeItem(
|
|
232
|
+
localStorage.removeItem(kOnboardingStorageKey);
|
|
301
233
|
onComplete();
|
|
302
234
|
} catch (err) {
|
|
303
235
|
console.error("Onboard error:", err);
|
|
@@ -306,237 +238,76 @@ export const Welcome = ({ onComplete }) => {
|
|
|
306
238
|
}
|
|
307
239
|
};
|
|
308
240
|
|
|
309
|
-
|
|
310
|
-
return
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
style="background: var(--bg)"
|
|
314
|
-
>
|
|
315
|
-
<div class="flex flex-col items-center gap-4">
|
|
316
|
-
<svg
|
|
317
|
-
class="animate-spin h-8 w-8 text-white"
|
|
318
|
-
viewBox="0 0 24 24"
|
|
319
|
-
fill="none"
|
|
320
|
-
>
|
|
321
|
-
<circle
|
|
322
|
-
class="opacity-25"
|
|
323
|
-
cx="12"
|
|
324
|
-
cy="12"
|
|
325
|
-
r="10"
|
|
326
|
-
stroke="currentColor"
|
|
327
|
-
stroke-width="4"
|
|
328
|
-
/>
|
|
329
|
-
<path
|
|
330
|
-
class="opacity-75"
|
|
331
|
-
fill="currentColor"
|
|
332
|
-
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
333
|
-
/>
|
|
334
|
-
</svg>
|
|
335
|
-
<h2 class="text-lg font-semibold text-white">
|
|
336
|
-
Initializing <span style="color: var(--accent)">alpha</span>claw
|
|
337
|
-
</h2>
|
|
338
|
-
<p class="text-sm text-gray-500">This could take 10–15 seconds</p>
|
|
339
|
-
</div>
|
|
340
|
-
</div>
|
|
341
|
-
`;
|
|
342
|
-
}
|
|
241
|
+
const goBack = () => {
|
|
242
|
+
if (isSetupStep) return;
|
|
243
|
+
setStep((prev) => Math.max(0, prev - 1));
|
|
244
|
+
};
|
|
343
245
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
<div>
|
|
349
|
-
<h1 class="text-2xl font-semibold">Welcome to <span style="color: var(--accent)">alpha</span>claw</h1>
|
|
350
|
-
<p style="color: var(--text-muted)" class="text-sm">Let's get your agent running</p>
|
|
351
|
-
</div>
|
|
352
|
-
</div>
|
|
246
|
+
const goNext = () => {
|
|
247
|
+
if (!activeGroup || !currentGroupValid) return;
|
|
248
|
+
setStep((prev) => Math.min(kWelcomeGroups.length - 1, prev + 1));
|
|
249
|
+
};
|
|
353
250
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
<div>
|
|
359
|
-
<h2 class="text-sm font-medium text-gray-200">
|
|
360
|
-
${group.title}
|
|
361
|
-
</h2>
|
|
362
|
-
<p class="text-xs text-gray-500">${group.description}</p>
|
|
363
|
-
</div>
|
|
364
|
-
${group.validate(vals, { hasAi })
|
|
365
|
-
? html`<span
|
|
366
|
-
class="text-xs font-medium px-2 py-0.5 rounded-full bg-green-900/50 text-green-400"
|
|
367
|
-
>✓</span
|
|
368
|
-
>`
|
|
369
|
-
: group.id !== "tools"
|
|
370
|
-
? html`<span
|
|
371
|
-
class="text-xs font-medium px-2 py-0.5 rounded-full bg-yellow-900/50 text-yellow-400"
|
|
372
|
-
>Required</span
|
|
373
|
-
>`
|
|
374
|
-
: null}
|
|
375
|
-
</div>
|
|
251
|
+
const activeStepLabel = isSetupStep
|
|
252
|
+
? "Initializing"
|
|
253
|
+
: activeGroup?.title || "Setup";
|
|
254
|
+
const stepNumber = isSetupStep ? kWelcomeGroups.length + 1 : step + 1;
|
|
376
255
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
(model) => html`
|
|
389
|
-
<option value=${model.key}>
|
|
390
|
-
${model.label || model.key}
|
|
391
|
-
</option>
|
|
392
|
-
`,
|
|
393
|
-
)}
|
|
394
|
-
</select>
|
|
395
|
-
<p class="text-xs text-gray-600">
|
|
396
|
-
${modelsLoading
|
|
397
|
-
? "Loading model catalog..."
|
|
398
|
-
: modelsError
|
|
399
|
-
? modelsError
|
|
400
|
-
: ""}
|
|
401
|
-
</p>
|
|
402
|
-
${canToggleFullCatalog &&
|
|
403
|
-
html`
|
|
404
|
-
<button
|
|
405
|
-
type="button"
|
|
406
|
-
onclick=${() => setShowAllModels((prev) => !prev)}
|
|
407
|
-
class="text-xs text-gray-500 hover:text-gray-300"
|
|
408
|
-
>
|
|
409
|
-
${showAllModels
|
|
410
|
-
? "Show recommended models"
|
|
411
|
-
: "Show full model catalog"}
|
|
412
|
-
</button>
|
|
413
|
-
`}
|
|
414
|
-
</div>
|
|
415
|
-
`}
|
|
416
|
-
${group.id === "ai" &&
|
|
417
|
-
selectedProvider === "openai-codex" &&
|
|
418
|
-
html`
|
|
419
|
-
<div
|
|
420
|
-
class="bg-black/20 border border-border rounded-lg p-3 space-y-2"
|
|
421
|
-
>
|
|
422
|
-
<div class="flex items-center justify-between">
|
|
423
|
-
<span class="text-xs text-gray-400">Codex OAuth</span>
|
|
424
|
-
${codexLoading
|
|
425
|
-
? html`<span class="text-xs text-gray-500"
|
|
426
|
-
>Checking...</span
|
|
427
|
-
>`
|
|
428
|
-
: codexStatus.connected
|
|
429
|
-
? html`<span class="text-xs text-green-400"
|
|
430
|
-
>Connected</span
|
|
431
|
-
>`
|
|
432
|
-
: html`<span class="text-xs text-yellow-400"
|
|
433
|
-
>Not connected</span
|
|
434
|
-
>`}
|
|
435
|
-
</div>
|
|
436
|
-
<div class="flex gap-2">
|
|
437
|
-
<button
|
|
438
|
-
type="button"
|
|
439
|
-
onclick=${startCodexAuth}
|
|
440
|
-
class="text-xs font-medium px-3 py-1.5 rounded-lg ${codexStatus.connected
|
|
441
|
-
? "border border-border text-gray-300 hover:border-gray-500"
|
|
442
|
-
: "bg-white text-black hover:opacity-85"}"
|
|
443
|
-
>
|
|
444
|
-
${codexStatus.connected
|
|
445
|
-
? "Reconnect Codex"
|
|
446
|
-
: "Connect Codex OAuth"}
|
|
447
|
-
</button>
|
|
448
|
-
${codexStatus.connected &&
|
|
449
|
-
html`
|
|
450
|
-
<button
|
|
451
|
-
type="button"
|
|
452
|
-
onclick=${handleCodexDisconnect}
|
|
453
|
-
class="text-xs font-medium px-3 py-1.5 rounded-lg border border-border text-gray-300 hover:border-gray-500"
|
|
454
|
-
>
|
|
455
|
-
Disconnect
|
|
456
|
-
</button>
|
|
457
|
-
`}
|
|
458
|
-
</div>
|
|
459
|
-
${!codexStatus.connected &&
|
|
460
|
-
codexAuthStarted &&
|
|
461
|
-
html`
|
|
462
|
-
<div class="space-y-1 pt-1">
|
|
463
|
-
<p class="text-xs text-gray-500">
|
|
464
|
-
${codexAuthWaiting
|
|
465
|
-
? "Complete login in the popup, then paste the full redirect URL from the address bar (starts with "
|
|
466
|
-
: "Paste the full redirect URL from the address bar (starts with "}
|
|
467
|
-
<code class="text-xs bg-black/30 px-1 rounded"
|
|
468
|
-
>http://localhost:1455/auth/callback</code
|
|
469
|
-
>)
|
|
470
|
-
${codexAuthWaiting
|
|
471
|
-
? " to finish setup."
|
|
472
|
-
: " to finish setup."}
|
|
473
|
-
</p>
|
|
474
|
-
<input
|
|
475
|
-
type="text"
|
|
476
|
-
value=${codexManualInput}
|
|
477
|
-
onInput=${(e) => setCodexManualInput(e.target.value)}
|
|
478
|
-
placeholder="http://localhost:1455/auth/callback?code=...&state=..."
|
|
479
|
-
class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-xs text-gray-200 outline-none focus:border-gray-500"
|
|
480
|
-
/>
|
|
481
|
-
<button
|
|
482
|
-
type="button"
|
|
483
|
-
onclick=${completeCodexAuth}
|
|
484
|
-
disabled=${!codexManualInput.trim() || codexExchanging}
|
|
485
|
-
class="text-xs font-medium px-3 py-1.5 rounded-lg ${!codexManualInput.trim() ||
|
|
486
|
-
codexExchanging
|
|
487
|
-
? "bg-gray-700 text-gray-400 cursor-not-allowed"
|
|
488
|
-
: "bg-white text-black hover:opacity-85"}"
|
|
489
|
-
>
|
|
490
|
-
${codexExchanging
|
|
491
|
-
? "Completing..."
|
|
492
|
-
: "Complete Codex OAuth"}
|
|
493
|
-
</button>
|
|
494
|
-
</div>
|
|
495
|
-
`}
|
|
496
|
-
</div>
|
|
497
|
-
`}
|
|
498
|
-
${(group.id === "ai"
|
|
499
|
-
? group.fields.filter((field) =>
|
|
500
|
-
visibleAiFieldKeys.has(field.key),
|
|
501
|
-
)
|
|
502
|
-
: group.fields
|
|
503
|
-
).map(
|
|
504
|
-
(field) => html`
|
|
505
|
-
<div class="space-y-1">
|
|
506
|
-
<label class="text-xs font-medium text-gray-400"
|
|
507
|
-
>${field.label}</label
|
|
508
|
-
>
|
|
509
|
-
<input
|
|
510
|
-
type=${field.isText ? "text" : "password"}
|
|
511
|
-
placeholder=${field.placeholder || ""}
|
|
512
|
-
value=${vals[field.key] || ""}
|
|
513
|
-
onInput=${(e) => set(field.key, e.target.value)}
|
|
514
|
-
class="w-full bg-black/30 border border-border rounded-lg px-3 py-2 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono"
|
|
515
|
-
/>
|
|
516
|
-
<p class="text-xs text-gray-600">${field.hint}</p>
|
|
517
|
-
</div>
|
|
518
|
-
`,
|
|
519
|
-
)}
|
|
520
|
-
</div>
|
|
521
|
-
`,
|
|
522
|
-
)}
|
|
523
|
-
${error
|
|
524
|
-
? html`<div
|
|
525
|
-
class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm"
|
|
526
|
-
>
|
|
527
|
-
${error}
|
|
528
|
-
</div>`
|
|
529
|
-
: null}
|
|
256
|
+
return html`
|
|
257
|
+
<div class="max-w-lg w-full space-y-5">
|
|
258
|
+
<${WelcomeHeader}
|
|
259
|
+
groups=${kWelcomeGroups}
|
|
260
|
+
step=${step}
|
|
261
|
+
isSetupStep=${isSetupStep}
|
|
262
|
+
stepNumber=${stepNumber}
|
|
263
|
+
activeStepLabel=${activeStepLabel}
|
|
264
|
+
vals=${vals}
|
|
265
|
+
hasAi=${hasAi}
|
|
266
|
+
/>
|
|
530
267
|
|
|
531
|
-
<
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
268
|
+
<div class="bg-surface border border-border rounded-xl p-4 space-y-3">
|
|
269
|
+
${isSetupStep
|
|
270
|
+
? html`<${WelcomeSetupStep}
|
|
271
|
+
error=${error}
|
|
272
|
+
loading=${loading}
|
|
273
|
+
onRetry=${handleSubmit}
|
|
274
|
+
/>`
|
|
275
|
+
: html`
|
|
276
|
+
<${WelcomeFormStep}
|
|
277
|
+
activeGroup=${activeGroup}
|
|
278
|
+
vals=${vals}
|
|
279
|
+
hasAi=${hasAi}
|
|
280
|
+
setValue=${set}
|
|
281
|
+
modelOptions=${modelOptions}
|
|
282
|
+
modelsLoading=${modelsLoading}
|
|
283
|
+
modelsError=${modelsError}
|
|
284
|
+
canToggleFullCatalog=${canToggleFullCatalog}
|
|
285
|
+
showAllModels=${showAllModels}
|
|
286
|
+
setShowAllModels=${setShowAllModels}
|
|
287
|
+
selectedProvider=${selectedProvider}
|
|
288
|
+
codexLoading=${codexLoading}
|
|
289
|
+
codexStatus=${codexStatus}
|
|
290
|
+
startCodexAuth=${startCodexAuth}
|
|
291
|
+
handleCodexDisconnect=${handleCodexDisconnect}
|
|
292
|
+
codexAuthStarted=${codexAuthStarted}
|
|
293
|
+
codexAuthWaiting=${codexAuthWaiting}
|
|
294
|
+
codexManualInput=${codexManualInput}
|
|
295
|
+
setCodexManualInput=${setCodexManualInput}
|
|
296
|
+
completeCodexAuth=${completeCodexAuth}
|
|
297
|
+
codexExchanging=${codexExchanging}
|
|
298
|
+
visibleAiFieldKeys=${visibleAiFieldKeys}
|
|
299
|
+
error=${error}
|
|
300
|
+
step=${step}
|
|
301
|
+
totalGroups=${kWelcomeGroups.length}
|
|
302
|
+
currentGroupValid=${currentGroupValid}
|
|
303
|
+
goBack=${goBack}
|
|
304
|
+
goNext=${goNext}
|
|
305
|
+
loading=${loading}
|
|
306
|
+
allValid=${allValid}
|
|
307
|
+
handleSubmit=${handleSubmit}
|
|
308
|
+
/>
|
|
309
|
+
`}
|
|
310
|
+
</div>
|
|
540
311
|
</div>
|
|
541
312
|
`;
|
|
542
313
|
};
|
package/lib/public/login.html
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<span style="color: var(--accent)">alpha</span>claw
|
|
41
41
|
</h1>
|
|
42
42
|
<p style="color: var(--text-muted)" class="text-xs mb-4">
|
|
43
|
-
OpenClaw made
|
|
43
|
+
OpenClaw made easy
|
|
44
44
|
</p>
|
|
45
45
|
</div>
|
|
46
46
|
<form
|
|
@@ -72,13 +72,21 @@
|
|
|
72
72
|
const submitButtonEl = document.getElementById("submit-btn");
|
|
73
73
|
|
|
74
74
|
const kEnabledClasses = ["bg-white", "text-black", "hover:opacity-85"];
|
|
75
|
-
const kDisabledClasses = [
|
|
75
|
+
const kDisabledClasses = [
|
|
76
|
+
"bg-gray-800",
|
|
77
|
+
"text-gray-500",
|
|
78
|
+
"cursor-not-allowed",
|
|
79
|
+
];
|
|
76
80
|
|
|
77
81
|
const syncButtonState = () => {
|
|
78
82
|
const hasValue = passwordEl.value.length > 0;
|
|
79
83
|
submitButtonEl.disabled = !hasValue;
|
|
80
|
-
kEnabledClasses.forEach((c) =>
|
|
81
|
-
|
|
84
|
+
kEnabledClasses.forEach((c) =>
|
|
85
|
+
submitButtonEl.classList.toggle(c, hasValue),
|
|
86
|
+
);
|
|
87
|
+
kDisabledClasses.forEach((c) =>
|
|
88
|
+
submitButtonEl.classList.toggle(c, !hasValue),
|
|
89
|
+
);
|
|
82
90
|
};
|
|
83
91
|
|
|
84
92
|
passwordEl.addEventListener("input", syncButtonState);
|