@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
package/lib/public/css/theme.css
CHANGED
package/lib/public/js/app.js
CHANGED
|
@@ -257,17 +257,17 @@ function App() {
|
|
|
257
257
|
useEffect(() => {
|
|
258
258
|
if (!onboarded) return;
|
|
259
259
|
let active = true;
|
|
260
|
-
const check = async () => {
|
|
260
|
+
const check = async (refresh = false) => {
|
|
261
261
|
try {
|
|
262
|
-
const data = await fetchAlphaclawVersion(
|
|
262
|
+
const data = await fetchAlphaclawVersion(refresh);
|
|
263
263
|
if (!active) return;
|
|
264
264
|
setAcVersion(data.currentVersion || null);
|
|
265
265
|
setAcLatest(data.latestVersion || null);
|
|
266
266
|
setAcHasUpdate(!!data.hasUpdate);
|
|
267
267
|
} catch {}
|
|
268
268
|
};
|
|
269
|
-
check();
|
|
270
|
-
const id = setInterval(check, 5 * 60 * 1000);
|
|
269
|
+
check(true);
|
|
270
|
+
const id = setInterval(() => check(false), 5 * 60 * 1000);
|
|
271
271
|
return () => { active = false; clearInterval(id); };
|
|
272
272
|
}, [onboarded]);
|
|
273
273
|
|
|
@@ -366,11 +366,15 @@ function App() {
|
|
|
366
366
|
|
|
367
367
|
<div class="app-content">
|
|
368
368
|
<div class="max-w-2xl w-full mx-auto space-y-4">
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
369
|
+
<div style=${{ display: tab === "general" ? "" : "none" }}>
|
|
370
|
+
<${GeneralTab} onSwitchTab=${setTab} />
|
|
371
|
+
</div>
|
|
372
|
+
<div style=${{ display: tab === "models" ? "" : "none" }}>
|
|
373
|
+
<${Models} />
|
|
374
|
+
</div>
|
|
375
|
+
<div style=${{ display: tab === "envars" ? "" : "none" }}>
|
|
376
|
+
<${Envars} />
|
|
377
|
+
</div>
|
|
374
378
|
</div>
|
|
375
379
|
</div>
|
|
376
380
|
|
|
@@ -2,6 +2,7 @@ import { h } from "https://esm.sh/preact";
|
|
|
2
2
|
import { useState, useRef } from "https://esm.sh/preact/hooks";
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import { saveGoogleCredentials } from "../lib/api.js";
|
|
5
|
+
import { SecretInput } from "./secret-input.js";
|
|
5
6
|
const html = htm.bind(h);
|
|
6
7
|
|
|
7
8
|
export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
@@ -110,7 +111,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
110
111
|
href="https://console.cloud.google.com/apis/credentials"
|
|
111
112
|
target="_blank"
|
|
112
113
|
class="hover:text-white"
|
|
113
|
-
style="color:
|
|
114
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
114
115
|
>Create one →</a
|
|
115
116
|
>
|
|
116
117
|
</p>
|
|
@@ -148,7 +149,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
148
149
|
href="https://console.cloud.google.com/projectcreate"
|
|
149
150
|
target="_blank"
|
|
150
151
|
class="hover:text-white"
|
|
151
|
-
style="color:
|
|
152
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
152
153
|
>Create a Google Cloud project</a
|
|
153
154
|
>${" "}(or use existing)
|
|
154
155
|
</li>
|
|
@@ -157,7 +158,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
157
158
|
href="https://console.cloud.google.com/auth/audience"
|
|
158
159
|
target="_blank"
|
|
159
160
|
class="hover:text-white"
|
|
160
|
-
style="color:
|
|
161
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
161
162
|
>OAuth consent screen</a
|
|
162
163
|
>${" "}→ set to <strong>External</strong>
|
|
163
164
|
</li>
|
|
@@ -166,7 +167,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
166
167
|
href="https://console.cloud.google.com/auth/audience"
|
|
167
168
|
target="_blank"
|
|
168
169
|
class="hover:text-white"
|
|
169
|
-
style="color:
|
|
170
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
170
171
|
>Test users</a
|
|
171
172
|
>, <strong>add your own email</strong>
|
|
172
173
|
</li>
|
|
@@ -175,7 +176,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
175
176
|
href="https://console.cloud.google.com/apis/library"
|
|
176
177
|
target="_blank"
|
|
177
178
|
class="hover:text-white"
|
|
178
|
-
style="color:
|
|
179
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
179
180
|
>Enable APIs</a
|
|
180
181
|
>${" "}for the services you selected below
|
|
181
182
|
</li>
|
|
@@ -184,7 +185,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
184
185
|
href="https://console.cloud.google.com/apis/credentials"
|
|
185
186
|
target="_blank"
|
|
186
187
|
class="hover:text-white"
|
|
187
|
-
style="color:
|
|
188
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
188
189
|
>Credentials</a
|
|
189
190
|
>${" "}→ Create OAuth 2.0 Client ID (Web application)
|
|
190
191
|
</li>
|
|
@@ -195,7 +196,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
195
196
|
Copy Client ID + Secret (or download credentials JSON)
|
|
196
197
|
</li>
|
|
197
198
|
</ol>
|
|
198
|
-
<p class="mt-3
|
|
199
|
+
<p class="mt-3 text-yellow-500/80">
|
|
199
200
|
⚠️ App will be in "Testing" mode. Only emails added as
|
|
200
201
|
Test Users can sign in (up to 100).
|
|
201
202
|
</p>
|
|
@@ -209,7 +210,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
209
210
|
href="https://console.cloud.google.com/projectcreate"
|
|
210
211
|
target="_blank"
|
|
211
212
|
class="hover:text-white"
|
|
212
|
-
style="color:
|
|
213
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
213
214
|
>Create a Google Cloud project</a
|
|
214
215
|
>${" "}(or use existing)
|
|
215
216
|
</li>
|
|
@@ -218,7 +219,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
218
219
|
href="https://console.cloud.google.com/auth/audience"
|
|
219
220
|
target="_blank"
|
|
220
221
|
class="hover:text-white"
|
|
221
|
-
style="color:
|
|
222
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
222
223
|
>OAuth consent screen</a
|
|
223
224
|
>${" "}→ set to <strong>Internal</strong> (Workspace
|
|
224
225
|
only)
|
|
@@ -228,7 +229,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
228
229
|
href="https://console.cloud.google.com/apis/library"
|
|
229
230
|
target="_blank"
|
|
230
231
|
class="hover:text-white"
|
|
231
|
-
style="color:
|
|
232
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
232
233
|
>Enable APIs</a
|
|
233
234
|
>${" "}for the services you selected below
|
|
234
235
|
</li>
|
|
@@ -237,7 +238,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
237
238
|
href="https://console.cloud.google.com/apis/credentials"
|
|
238
239
|
target="_blank"
|
|
239
240
|
class="hover:text-white"
|
|
240
|
-
style="color:
|
|
241
|
+
style="color: rgba(99, 235, 255, 0.6)"
|
|
241
242
|
>Credentials</a
|
|
242
243
|
>${" "}→ Create OAuth 2.0 Client ID (Web application)
|
|
243
244
|
</li>
|
|
@@ -248,7 +249,7 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
248
249
|
Copy Client ID + Secret (or download credentials JSON)
|
|
249
250
|
</li>
|
|
250
251
|
</ol>
|
|
251
|
-
<p class="mt-3
|
|
252
|
+
<p class="mt-3 text-green-500/80">
|
|
252
253
|
✓ Internal apps skip test users and verification. Only
|
|
253
254
|
users in your Workspace org can authorize this Google app.
|
|
254
255
|
</p>
|
|
@@ -285,24 +286,22 @@ export const CredentialsModal = ({ visible, onClose, onSaved }) => {
|
|
|
285
286
|
</div>
|
|
286
287
|
<div>
|
|
287
288
|
<label class="text-sm text-gray-400 block mb-1">Client ID</label>
|
|
288
|
-
|
|
289
|
-
type="text"
|
|
289
|
+
<${SecretInput}
|
|
290
290
|
value=${clientId}
|
|
291
291
|
onInput=${(e) => setClientId(e.target.value)}
|
|
292
292
|
placeholder="xxxx.apps.googleusercontent.com"
|
|
293
|
-
|
|
293
|
+
inputClass="flex-1 bg-black/40 border border-border rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-gray-500"
|
|
294
294
|
/>
|
|
295
295
|
</div>
|
|
296
296
|
<div>
|
|
297
297
|
<label class="text-sm text-gray-400 block mb-1"
|
|
298
298
|
>Client Secret</label
|
|
299
299
|
>
|
|
300
|
-
|
|
301
|
-
type="password"
|
|
300
|
+
<${SecretInput}
|
|
302
301
|
value=${clientSecret}
|
|
303
302
|
onInput=${(e) => setClientSecret(e.target.value)}
|
|
304
303
|
placeholder="GOCSPX-..."
|
|
305
|
-
|
|
304
|
+
inputClass="flex-1 bg-black/40 border border-border rounded-lg px-3 py-2 text-sm focus:outline-none focus:border-gray-500"
|
|
306
305
|
/>
|
|
307
306
|
</div>
|
|
308
307
|
<div>
|
|
@@ -3,6 +3,7 @@ import { useState, useEffect, useCallback } from "https://esm.sh/preact/hooks";
|
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import { fetchEnvVars, saveEnvVars, restartGateway } from "../lib/api.js";
|
|
5
5
|
import { showToast } from "./toast.js";
|
|
6
|
+
import { SecretInput } from "./secret-input.js";
|
|
6
7
|
const html = htm.bind(h);
|
|
7
8
|
|
|
8
9
|
const kGroupLabels = {
|
|
@@ -15,22 +16,20 @@ const kGroupLabels = {
|
|
|
15
16
|
const kGroupOrder = ["github", "channels", "tools", "custom"];
|
|
16
17
|
|
|
17
18
|
const kHintByKey = {
|
|
18
|
-
ANTHROPIC_API_KEY: html`from <a href="https://console.anthropic.com" target="_blank" class="hover:underline" style="color:
|
|
19
|
+
ANTHROPIC_API_KEY: html`from <a href="https://console.anthropic.com" target="_blank" class="hover:underline" style="color: var(--accent-link)">console.anthropic.com</a>`,
|
|
19
20
|
ANTHROPIC_TOKEN: html`from <code class="text-xs bg-black/30 px-1 rounded">claude setup-token</code>`,
|
|
20
|
-
OPENAI_API_KEY: html`from <a href="https://platform.openai.com" target="_blank" class="hover:underline" style="color:
|
|
21
|
-
GEMINI_API_KEY: html`from <a href="https://aistudio.google.com" target="_blank" class="hover:underline" style="color:
|
|
22
|
-
GITHUB_TOKEN: html`classic PAT · <code class="text-xs bg-black/30 px-1 rounded">repo</code> scope · <a href="https://github.com/settings/tokens" target="_blank" class="hover:underline" style="color:
|
|
21
|
+
OPENAI_API_KEY: html`from <a href="https://platform.openai.com" target="_blank" class="hover:underline" style="color: var(--accent-link)">platform.openai.com</a>`,
|
|
22
|
+
GEMINI_API_KEY: html`from <a href="https://aistudio.google.com" target="_blank" class="hover:underline" style="color: var(--accent-link)">aistudio.google.com</a>`,
|
|
23
|
+
GITHUB_TOKEN: html`classic PAT · <code class="text-xs bg-black/30 px-1 rounded">repo</code> scope · <a href="https://github.com/settings/tokens" target="_blank" class="hover:underline" style="color: var(--accent-link)">github settings</a>`,
|
|
23
24
|
GITHUB_WORKSPACE_REPO: html`use <code class="text-xs bg-black/30 px-1 rounded">owner/repo</code> or <code class="text-xs bg-black/30 px-1 rounded">https://github.com/owner/repo</code>`,
|
|
24
|
-
TELEGRAM_BOT_TOKEN: html`from <a href="https://t.me/BotFather" target="_blank" class="hover:underline" style="color:
|
|
25
|
-
DISCORD_BOT_TOKEN: html`from <a href="https://discord.com/developers/applications" target="_blank" class="hover:underline" style="color:
|
|
26
|
-
BRAVE_API_KEY: html`from <a href="https://brave.com/search/api/" target="_blank" class="hover:underline" style="color:
|
|
25
|
+
TELEGRAM_BOT_TOKEN: html`from <a href="https://t.me/BotFather" target="_blank" class="hover:underline" style="color: var(--accent-link)">@BotFather</a> · <a href="https://docs.openclaw.ai/channels/telegram" target="_blank" class="hover:underline" style="color: var(--accent-link)">full guide</a>`,
|
|
26
|
+
DISCORD_BOT_TOKEN: html`from <a href="https://discord.com/developers/applications" target="_blank" class="hover:underline" style="color: var(--accent-link)">developer portal</a> · <a href="https://docs.openclaw.ai/channels/discord" target="_blank" class="hover:underline" style="color: var(--accent-link)">full guide</a>`,
|
|
27
|
+
BRAVE_API_KEY: html`from <a href="https://brave.com/search/api/" target="_blank" class="hover:underline" style="color: var(--accent-link)">brave.com/search/api</a> — free tier available`,
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
const getHintContent = (envVar) => kHintByKey[envVar.key] || envVar.hint || "";
|
|
30
31
|
|
|
31
32
|
const EnvRow = ({ envVar, onChange, onDelete, disabled }) => {
|
|
32
|
-
const [visible, setVisible] = useState(false);
|
|
33
|
-
const isSecret = !!envVar.value;
|
|
34
33
|
const hint = getHintContent(envVar);
|
|
35
34
|
|
|
36
35
|
return html`
|
|
@@ -45,23 +44,14 @@ const EnvRow = ({ envVar, onChange, onDelete, disabled }) => {
|
|
|
45
44
|
</div>
|
|
46
45
|
<div class="flex-1 min-w-0">
|
|
47
46
|
<div class="flex items-center gap-1">
|
|
48
|
-
|
|
49
|
-
type=${isSecret && !visible ? "password" : "text"}
|
|
47
|
+
<${SecretInput}
|
|
50
48
|
value=${envVar.value}
|
|
51
|
-
placeholder=${envVar.value ? "" : "not set"}
|
|
52
49
|
onInput=${(e) => onChange(envVar.key, e.target.value)}
|
|
53
|
-
|
|
50
|
+
placeholder=${envVar.value ? "" : "not set"}
|
|
51
|
+
isSecret=${!!envVar.value}
|
|
52
|
+
inputClass="flex-1 min-w-0 bg-black/30 border border-border rounded-lg px-3 py-1.5 text-sm text-gray-200 outline-none focus:border-gray-500 font-mono"
|
|
54
53
|
disabled=${disabled}
|
|
55
54
|
/>
|
|
56
|
-
${isSecret
|
|
57
|
-
? html`<button
|
|
58
|
-
onclick=${() => setVisible(!visible)}
|
|
59
|
-
class="text-gray-500 hover:text-gray-300 px-1 text-xs shrink-0"
|
|
60
|
-
title=${visible ? "Hide" : "Show"}
|
|
61
|
-
>
|
|
62
|
-
${visible ? "Hide" : "Show"}
|
|
63
|
-
</button>`
|
|
64
|
-
: null}
|
|
65
55
|
${envVar.group === "custom"
|
|
66
56
|
? html`<button
|
|
67
57
|
onclick=${() => onDelete(envVar.key)}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { kAllAiAuthFields } from "../../lib/model-config.js";
|
|
4
|
+
|
|
5
|
+
const html = htm.bind(h);
|
|
6
|
+
|
|
7
|
+
export const kWelcomeGroups = [
|
|
8
|
+
{
|
|
9
|
+
id: "ai",
|
|
10
|
+
title: "Primary Agent Model",
|
|
11
|
+
description: "Choose your main model and authenticate its provider",
|
|
12
|
+
fields: kAllAiAuthFields,
|
|
13
|
+
validate: (vals, ctx = {}) => !!(vals.MODEL_KEY && ctx.hasAi),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "github",
|
|
17
|
+
title: "GitHub",
|
|
18
|
+
description: "Backs up your agent's config and workspace",
|
|
19
|
+
fields: [
|
|
20
|
+
{
|
|
21
|
+
key: "GITHUB_WORKSPACE_REPO",
|
|
22
|
+
label: "Workspace Repo",
|
|
23
|
+
hint: "A new private repo will be created for you",
|
|
24
|
+
placeholder: "username/my-agent",
|
|
25
|
+
isText: true,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: "GITHUB_TOKEN",
|
|
29
|
+
label: "Personal Access Token",
|
|
30
|
+
hint: html`Create a classic PAT on${" "}<a
|
|
31
|
+
href="https://github.com/settings/tokens"
|
|
32
|
+
target="_blank"
|
|
33
|
+
class="hover:underline"
|
|
34
|
+
style="color: var(--accent-link)"
|
|
35
|
+
>GitHub settings</a
|
|
36
|
+
>${" "}with${" "}<code class="text-xs bg-black/30 px-1 rounded"
|
|
37
|
+
>repo</code
|
|
38
|
+
>${" "}scope`,
|
|
39
|
+
placeholder: "ghp_...",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
validate: (vals) => !!(vals.GITHUB_TOKEN && vals.GITHUB_WORKSPACE_REPO),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "channels",
|
|
46
|
+
title: "Channels",
|
|
47
|
+
description: "At least one is required to talk to your agent",
|
|
48
|
+
fields: [
|
|
49
|
+
{
|
|
50
|
+
key: "TELEGRAM_BOT_TOKEN",
|
|
51
|
+
label: "Telegram Bot Token",
|
|
52
|
+
hint: html`From${" "}<a
|
|
53
|
+
href="https://t.me/BotFather"
|
|
54
|
+
target="_blank"
|
|
55
|
+
class="hover:underline"
|
|
56
|
+
style="color: var(--accent-link)"
|
|
57
|
+
>@BotFather</a
|
|
58
|
+
>${" "}·${" "}<a
|
|
59
|
+
href="https://docs.openclaw.ai/channels/telegram"
|
|
60
|
+
target="_blank"
|
|
61
|
+
class="hover:underline"
|
|
62
|
+
style="color: var(--accent-link)"
|
|
63
|
+
>full guide</a
|
|
64
|
+
>`,
|
|
65
|
+
placeholder: "123456789:AAH...",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
key: "DISCORD_BOT_TOKEN",
|
|
69
|
+
label: "Discord Bot Token",
|
|
70
|
+
hint: html`From${" "}<a
|
|
71
|
+
href="https://discord.com/developers/applications"
|
|
72
|
+
target="_blank"
|
|
73
|
+
class="hover:underline"
|
|
74
|
+
style="color: var(--accent-link)"
|
|
75
|
+
>Developer Portal</a
|
|
76
|
+
>${" "}·${" "}<a
|
|
77
|
+
href="https://docs.openclaw.ai/channels/discord"
|
|
78
|
+
target="_blank"
|
|
79
|
+
class="hover:underline"
|
|
80
|
+
style="color: var(--accent-link)"
|
|
81
|
+
>full guide</a
|
|
82
|
+
>`,
|
|
83
|
+
placeholder: "MTQ3...",
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
validate: (vals) => !!(vals.TELEGRAM_BOT_TOKEN || vals.DISCORD_BOT_TOKEN),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "tools",
|
|
90
|
+
title: "Tools (optional)",
|
|
91
|
+
description: "Enable extra capabilities for your agent",
|
|
92
|
+
fields: [
|
|
93
|
+
{
|
|
94
|
+
key: "BRAVE_API_KEY",
|
|
95
|
+
label: "Brave Search API Key",
|
|
96
|
+
hint: html`From${" "}<a
|
|
97
|
+
href="https://brave.com/search/api/"
|
|
98
|
+
target="_blank"
|
|
99
|
+
class="hover:underline"
|
|
100
|
+
style="color: var(--accent-link)"
|
|
101
|
+
>brave.com/search/api</a
|
|
102
|
+
>${" "}-${" "}free tier available`,
|
|
103
|
+
placeholder: "BSA...",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
validate: () => true,
|
|
107
|
+
},
|
|
108
|
+
];
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { h } from "https://esm.sh/preact";
|
|
2
|
+
import htm from "https://esm.sh/htm";
|
|
3
|
+
import { SecretInput } from "../secret-input.js";
|
|
4
|
+
|
|
5
|
+
const html = htm.bind(h);
|
|
6
|
+
|
|
7
|
+
export const WelcomeFormStep = ({
|
|
8
|
+
activeGroup,
|
|
9
|
+
vals,
|
|
10
|
+
hasAi,
|
|
11
|
+
setValue,
|
|
12
|
+
modelOptions,
|
|
13
|
+
modelsLoading,
|
|
14
|
+
modelsError,
|
|
15
|
+
canToggleFullCatalog,
|
|
16
|
+
showAllModels,
|
|
17
|
+
setShowAllModels,
|
|
18
|
+
selectedProvider,
|
|
19
|
+
codexLoading,
|
|
20
|
+
codexStatus,
|
|
21
|
+
startCodexAuth,
|
|
22
|
+
handleCodexDisconnect,
|
|
23
|
+
codexAuthStarted,
|
|
24
|
+
codexAuthWaiting,
|
|
25
|
+
codexManualInput,
|
|
26
|
+
setCodexManualInput,
|
|
27
|
+
completeCodexAuth,
|
|
28
|
+
codexExchanging,
|
|
29
|
+
visibleAiFieldKeys,
|
|
30
|
+
error,
|
|
31
|
+
step,
|
|
32
|
+
totalGroups,
|
|
33
|
+
currentGroupValid,
|
|
34
|
+
goBack,
|
|
35
|
+
goNext,
|
|
36
|
+
loading,
|
|
37
|
+
allValid,
|
|
38
|
+
handleSubmit,
|
|
39
|
+
}) => html`
|
|
40
|
+
<div class="flex items-center justify-between">
|
|
41
|
+
<div>
|
|
42
|
+
<h2 class="text-sm font-medium text-gray-200">${activeGroup.title}</h2>
|
|
43
|
+
<p class="text-xs text-gray-500">${activeGroup.description}</p>
|
|
44
|
+
</div>
|
|
45
|
+
${activeGroup.validate(vals, { hasAi })
|
|
46
|
+
? html`<span
|
|
47
|
+
class="text-xs font-medium px-2 py-0.5 rounded-full bg-green-900/50 text-green-400"
|
|
48
|
+
>✓</span
|
|
49
|
+
>`
|
|
50
|
+
: activeGroup.id !== "tools"
|
|
51
|
+
? html`<span
|
|
52
|
+
class="text-xs font-medium px-2 py-0.5 rounded-full bg-yellow-900/50 text-yellow-400"
|
|
53
|
+
>Required</span
|
|
54
|
+
>`
|
|
55
|
+
: null}
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
${activeGroup.id === "ai" &&
|
|
59
|
+
html`
|
|
60
|
+
<div class="space-y-1">
|
|
61
|
+
<label class="text-xs font-medium text-gray-400">Model</label>
|
|
62
|
+
<select
|
|
63
|
+
value=${vals.MODEL_KEY || ""}
|
|
64
|
+
onInput=${(e) => setValue("MODEL_KEY", e.target.value)}
|
|
65
|
+
class="w-full bg-black/30 border border-border rounded-lg pl-3 pr-8 py-2 text-sm text-gray-200 outline-none focus:border-gray-500"
|
|
66
|
+
>
|
|
67
|
+
<option value="">Select a model</option>
|
|
68
|
+
${modelOptions.map(
|
|
69
|
+
(model) => html`
|
|
70
|
+
<option value=${model.key}>${model.label || model.key}</option>
|
|
71
|
+
`,
|
|
72
|
+
)}
|
|
73
|
+
</select>
|
|
74
|
+
<p class="text-xs text-gray-600">
|
|
75
|
+
${modelsLoading
|
|
76
|
+
? "Loading model catalog..."
|
|
77
|
+
: modelsError
|
|
78
|
+
? modelsError
|
|
79
|
+
: ""}
|
|
80
|
+
</p>
|
|
81
|
+
${canToggleFullCatalog &&
|
|
82
|
+
html`
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
onclick=${() => setShowAllModels((prev) => !prev)}
|
|
86
|
+
class="text-xs text-gray-500 hover:text-gray-300"
|
|
87
|
+
>
|
|
88
|
+
${showAllModels
|
|
89
|
+
? "Show recommended models"
|
|
90
|
+
: "Show full model catalog"}
|
|
91
|
+
</button>
|
|
92
|
+
`}
|
|
93
|
+
</div>
|
|
94
|
+
`}
|
|
95
|
+
${activeGroup.id === "ai" &&
|
|
96
|
+
selectedProvider === "openai-codex" &&
|
|
97
|
+
html`
|
|
98
|
+
<div class="bg-black/20 border border-border rounded-lg p-3 space-y-2">
|
|
99
|
+
<div class="flex items-center justify-between">
|
|
100
|
+
<span class="text-xs text-gray-400">Codex OAuth</span>
|
|
101
|
+
${codexLoading
|
|
102
|
+
? html`<span class="text-xs text-gray-500">Checking...</span>`
|
|
103
|
+
: codexStatus.connected
|
|
104
|
+
? html`<span class="text-xs text-green-400">Connected</span>`
|
|
105
|
+
: html`<span class="text-xs text-yellow-400">Not connected</span>`}
|
|
106
|
+
</div>
|
|
107
|
+
<div class="flex gap-2">
|
|
108
|
+
<button
|
|
109
|
+
type="button"
|
|
110
|
+
onclick=${startCodexAuth}
|
|
111
|
+
class="text-xs font-medium px-3 py-1.5 rounded-lg ${codexStatus.connected
|
|
112
|
+
? "border border-border text-gray-300 hover:border-gray-500"
|
|
113
|
+
: "bg-white text-black hover:opacity-85"}"
|
|
114
|
+
>
|
|
115
|
+
${codexStatus.connected ? "Reconnect Codex" : "Connect Codex OAuth"}
|
|
116
|
+
</button>
|
|
117
|
+
${codexStatus.connected &&
|
|
118
|
+
html`
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
onclick=${handleCodexDisconnect}
|
|
122
|
+
class="text-xs font-medium px-3 py-1.5 rounded-lg border border-border text-gray-300 hover:border-gray-500"
|
|
123
|
+
>
|
|
124
|
+
Disconnect
|
|
125
|
+
</button>
|
|
126
|
+
`}
|
|
127
|
+
</div>
|
|
128
|
+
${!codexStatus.connected &&
|
|
129
|
+
codexAuthStarted &&
|
|
130
|
+
html`
|
|
131
|
+
<div class="space-y-1 pt-1">
|
|
132
|
+
<p class="text-xs text-gray-500">
|
|
133
|
+
${codexAuthWaiting
|
|
134
|
+
? "Complete login in the popup, then paste the full redirect URL from the address bar (starts with "
|
|
135
|
+
: "Paste the full redirect URL from the address bar (starts with "}
|
|
136
|
+
<code class="text-xs bg-black/30 px-1 rounded"
|
|
137
|
+
>http://localhost:1455/auth/callback</code
|
|
138
|
+
>) ${codexAuthWaiting ? " to finish setup." : " to finish setup."}
|
|
139
|
+
</p>
|
|
140
|
+
<input
|
|
141
|
+
type="text"
|
|
142
|
+
value=${codexManualInput}
|
|
143
|
+
onInput=${(e) => setCodexManualInput(e.target.value)}
|
|
144
|
+
placeholder="http://localhost:1455/auth/callback?code=...&state=..."
|
|
145
|
+
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"
|
|
146
|
+
/>
|
|
147
|
+
<button
|
|
148
|
+
type="button"
|
|
149
|
+
onclick=${completeCodexAuth}
|
|
150
|
+
disabled=${!codexManualInput.trim() || codexExchanging}
|
|
151
|
+
class="text-xs font-medium px-3 py-1.5 rounded-lg ${!codexManualInput.trim() ||
|
|
152
|
+
codexExchanging
|
|
153
|
+
? "bg-gray-700 text-gray-400 cursor-not-allowed"
|
|
154
|
+
: "bg-white text-black hover:opacity-85"}"
|
|
155
|
+
>
|
|
156
|
+
${codexExchanging ? "Completing..." : "Complete Codex OAuth"}
|
|
157
|
+
</button>
|
|
158
|
+
</div>
|
|
159
|
+
`}
|
|
160
|
+
</div>
|
|
161
|
+
`}
|
|
162
|
+
${(activeGroup.id === "ai"
|
|
163
|
+
? activeGroup.fields.filter((field) => visibleAiFieldKeys.has(field.key))
|
|
164
|
+
: activeGroup.fields
|
|
165
|
+
).map(
|
|
166
|
+
(field) => html`
|
|
167
|
+
<div class="space-y-1" key=${field.key}>
|
|
168
|
+
<label class="text-xs font-medium text-gray-400">${field.label}</label>
|
|
169
|
+
<${SecretInput}
|
|
170
|
+
key=${field.key}
|
|
171
|
+
value=${vals[field.key] || ""}
|
|
172
|
+
onInput=${(e) => setValue(field.key, e.target.value)}
|
|
173
|
+
placeholder=${field.placeholder || ""}
|
|
174
|
+
isSecret=${!field.isText}
|
|
175
|
+
inputClass="flex-1 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"
|
|
176
|
+
/>
|
|
177
|
+
<p class="text-xs text-gray-600">${field.hint}</p>
|
|
178
|
+
</div>
|
|
179
|
+
`,
|
|
180
|
+
)}
|
|
181
|
+
${error
|
|
182
|
+
? html`<div
|
|
183
|
+
class="bg-red-900/30 border border-red-800 rounded-xl p-3 text-red-300 text-sm"
|
|
184
|
+
>
|
|
185
|
+
${error}
|
|
186
|
+
</div>`
|
|
187
|
+
: null}
|
|
188
|
+
${step === totalGroups - 1 && (!vals.OPENAI_API_KEY || !vals.GEMINI_API_KEY)
|
|
189
|
+
? html`
|
|
190
|
+
${!vals.OPENAI_API_KEY
|
|
191
|
+
? html`<div class="space-y-1">
|
|
192
|
+
<label class="text-xs font-medium text-gray-400"
|
|
193
|
+
>OpenAI API Key</label
|
|
194
|
+
>
|
|
195
|
+
<${SecretInput}
|
|
196
|
+
value=${vals.OPENAI_API_KEY || ""}
|
|
197
|
+
onInput=${(e) => setValue("OPENAI_API_KEY", e.target.value)}
|
|
198
|
+
placeholder="sk-..."
|
|
199
|
+
isSecret=${true}
|
|
200
|
+
inputClass="flex-1 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"
|
|
201
|
+
/>
|
|
202
|
+
<p class="text-xs text-gray-600">
|
|
203
|
+
Used for memory embeddings -${" "}
|
|
204
|
+
<a
|
|
205
|
+
href="https://platform.openai.com"
|
|
206
|
+
target="_blank"
|
|
207
|
+
class="hover:underline"
|
|
208
|
+
style="color: var(--accent-link)"
|
|
209
|
+
>get key</a
|
|
210
|
+
>
|
|
211
|
+
</p>
|
|
212
|
+
</div>`
|
|
213
|
+
: null}
|
|
214
|
+
${!vals.GEMINI_API_KEY
|
|
215
|
+
? html`<div class="space-y-1">
|
|
216
|
+
<label class="text-xs font-medium text-gray-400"
|
|
217
|
+
>Gemini API Key</label
|
|
218
|
+
>
|
|
219
|
+
<${SecretInput}
|
|
220
|
+
value=${vals.GEMINI_API_KEY || ""}
|
|
221
|
+
onInput=${(e) => setValue("GEMINI_API_KEY", e.target.value)}
|
|
222
|
+
placeholder="AI..."
|
|
223
|
+
isSecret=${true}
|
|
224
|
+
inputClass="flex-1 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"
|
|
225
|
+
/>
|
|
226
|
+
<p class="text-xs text-gray-600">
|
|
227
|
+
Used for memory embeddings and Nano Banana -${" "}
|
|
228
|
+
<a
|
|
229
|
+
href="https://aistudio.google.com"
|
|
230
|
+
target="_blank"
|
|
231
|
+
class="hover:underline"
|
|
232
|
+
style="color: var(--accent-link)"
|
|
233
|
+
>get key</a
|
|
234
|
+
>
|
|
235
|
+
</p>
|
|
236
|
+
</div>`
|
|
237
|
+
: null}
|
|
238
|
+
`
|
|
239
|
+
: null}
|
|
240
|
+
|
|
241
|
+
<div class="grid grid-cols-2 gap-2 pt-3">
|
|
242
|
+
${step < totalGroups - 1
|
|
243
|
+
? html`
|
|
244
|
+
${step > 0
|
|
245
|
+
? html`<button
|
|
246
|
+
onclick=${goBack}
|
|
247
|
+
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all border border-border text-gray-300 hover:border-gray-500"
|
|
248
|
+
>
|
|
249
|
+
Back
|
|
250
|
+
</button>`
|
|
251
|
+
: html`<div class="w-full"></div>`}
|
|
252
|
+
<button
|
|
253
|
+
onclick=${goNext}
|
|
254
|
+
disabled=${!currentGroupValid}
|
|
255
|
+
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ${currentGroupValid
|
|
256
|
+
? "bg-white text-black hover:opacity-85"
|
|
257
|
+
: "bg-gray-800 text-gray-500 cursor-not-allowed"}"
|
|
258
|
+
>
|
|
259
|
+
Next
|
|
260
|
+
</button>
|
|
261
|
+
`
|
|
262
|
+
: html`
|
|
263
|
+
${step > 0
|
|
264
|
+
? html`<button
|
|
265
|
+
onclick=${goBack}
|
|
266
|
+
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all border border-border text-gray-300 hover:border-gray-500"
|
|
267
|
+
>
|
|
268
|
+
Back
|
|
269
|
+
</button>`
|
|
270
|
+
: html`<div class="w-full"></div>`}
|
|
271
|
+
<button
|
|
272
|
+
onclick=${handleSubmit}
|
|
273
|
+
disabled=${!allValid || loading}
|
|
274
|
+
class="w-full text-sm font-medium px-4 py-2 rounded-xl transition-all ${allValid &&
|
|
275
|
+
!loading
|
|
276
|
+
? "bg-white text-black hover:opacity-85"
|
|
277
|
+
: "bg-gray-800 text-gray-500 cursor-not-allowed"}"
|
|
278
|
+
>
|
|
279
|
+
${loading ? "Starting..." : "Complete Setup"}
|
|
280
|
+
</button>
|
|
281
|
+
`}
|
|
282
|
+
</div>
|
|
283
|
+
`;
|