@chrysb/alphaclaw 0.5.1 → 0.5.2
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/js/components/models-tab/index.js +203 -48
- package/lib/public/js/components/models-tab/provider-auth-card.js +20 -2
- package/lib/public/js/components/models.js +8 -9
- package/lib/public/js/components/onboarding/welcome-import-step.js +6 -5
- package/lib/public/js/components/onboarding/welcome-secret-review-step.js +12 -6
- package/lib/public/js/components/onboarding/welcome-secret-review-utils.js +19 -0
- package/lib/public/js/components/providers.js +9 -13
- package/lib/public/js/components/usage-tab/overview-section.js +2 -1
- package/lib/public/js/components/welcome/use-welcome.js +3 -0
- package/lib/public/js/lib/model-config.js +149 -2
- package/lib/server/auth-profiles.js +14 -0
- package/lib/server/constants.js +23 -4
- package/lib/server/gateway.js +18 -2
- package/lib/server/onboarding/import/import-applier.js +127 -0
- package/lib/server/onboarding/import/import-scanner.js +8 -1
- package/lib/server/onboarding/import/secret-detector.js +52 -6
- package/lib/server/onboarding/index.js +126 -0
- package/lib/server/onboarding/openclaw.js +88 -5
- package/lib/server/routes/onboarding.js +12 -3
- package/lib/server/routes/proxy.js +7 -4
- package/lib/server/routes/system.js +14 -0
- package/lib/server/webhook-middleware.js +5 -2
- package/lib/server.js +6 -4
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { h } from "https://esm.sh/preact";
|
|
2
|
-
import { useState, useMemo } from "https://esm.sh/preact/hooks";
|
|
2
|
+
import { useState, useMemo, useRef, useEffect } from "https://esm.sh/preact/hooks";
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import { PageHeader } from "../page-header.js";
|
|
5
5
|
import { LoadingSpinner } from "../loading-spinner.js";
|
|
@@ -7,14 +7,26 @@ import { ActionButton } from "../action-button.js";
|
|
|
7
7
|
import { Badge } from "../badge.js";
|
|
8
8
|
import { useModels } from "./use-models.js";
|
|
9
9
|
import { ProviderAuthCard } from "./provider-auth-card.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
getModelProvider,
|
|
12
|
+
getAuthProviderFromModelProvider,
|
|
13
|
+
getFeaturedModels,
|
|
14
|
+
kProviderLabels,
|
|
15
|
+
kProviderOrder,
|
|
16
|
+
} from "../../lib/model-config.js";
|
|
11
17
|
|
|
12
18
|
const html = htm.bind(h);
|
|
13
19
|
|
|
20
|
+
const getModelsTabAuthProvider = (modelKey) => {
|
|
21
|
+
const provider = getModelProvider(modelKey);
|
|
22
|
+
if (provider === "openai-codex") return "openai-codex";
|
|
23
|
+
return getAuthProviderFromModelProvider(provider);
|
|
24
|
+
};
|
|
25
|
+
|
|
14
26
|
const deriveRequiredProviders = (configuredModels) => {
|
|
15
27
|
const providers = new Set();
|
|
16
28
|
for (const modelKey of Object.keys(configuredModels)) {
|
|
17
|
-
const provider =
|
|
29
|
+
const provider = getModelsTabAuthProvider(modelKey);
|
|
18
30
|
if (provider) providers.add(provider);
|
|
19
31
|
}
|
|
20
32
|
return [...providers];
|
|
@@ -24,9 +36,172 @@ const kProviderDisplayOrder = [
|
|
|
24
36
|
"anthropic",
|
|
25
37
|
"openai",
|
|
26
38
|
"openai-codex",
|
|
27
|
-
"
|
|
39
|
+
...kProviderOrder.filter((provider) => !["anthropic", "openai"].includes(provider)),
|
|
28
40
|
];
|
|
29
41
|
|
|
42
|
+
const getModelCatalogProvider = (model) =>
|
|
43
|
+
String(model?.provider || getModelProvider(model?.key)).trim();
|
|
44
|
+
|
|
45
|
+
const getProviderSortIndex = (provider) => {
|
|
46
|
+
const index = kProviderDisplayOrder.indexOf(provider);
|
|
47
|
+
return index >= 0 ? index : Number.MAX_SAFE_INTEGER;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const formatProviderSectionLabel = (provider) =>
|
|
51
|
+
String(kProviderLabels[provider] || provider).toUpperCase();
|
|
52
|
+
|
|
53
|
+
const normalizeSearch = (value) => String(value || "").trim().toLowerCase();
|
|
54
|
+
const buildModelSearchText = (model) =>
|
|
55
|
+
[
|
|
56
|
+
model?.label || "",
|
|
57
|
+
model?.key || "",
|
|
58
|
+
model?.provider || getModelProvider(model?.key),
|
|
59
|
+
]
|
|
60
|
+
.join(" ")
|
|
61
|
+
.toLowerCase();
|
|
62
|
+
|
|
63
|
+
const SearchableModelPicker = ({
|
|
64
|
+
options = [],
|
|
65
|
+
popularModels = [],
|
|
66
|
+
placeholder = "Add model...",
|
|
67
|
+
onSelect = () => {},
|
|
68
|
+
}) => {
|
|
69
|
+
const [query, setQuery] = useState("");
|
|
70
|
+
const [open, setOpen] = useState(false);
|
|
71
|
+
const rootRef = useRef(null);
|
|
72
|
+
const normalizedQuery = normalizeSearch(query);
|
|
73
|
+
const filteredOptions = useMemo(
|
|
74
|
+
() =>
|
|
75
|
+
normalizedQuery
|
|
76
|
+
? options.filter((option) =>
|
|
77
|
+
buildModelSearchText(option).includes(normalizedQuery),
|
|
78
|
+
)
|
|
79
|
+
: options,
|
|
80
|
+
[options, normalizedQuery],
|
|
81
|
+
);
|
|
82
|
+
const groupedOptions = useMemo(() => {
|
|
83
|
+
const groups = [];
|
|
84
|
+
const showPopularGroup = !normalizedQuery;
|
|
85
|
+
const visibleOptionKeys = new Set(filteredOptions.map((option) => option.key));
|
|
86
|
+
const visiblePopularModels = popularModels.filter((model) =>
|
|
87
|
+
visibleOptionKeys.has(model.key),
|
|
88
|
+
);
|
|
89
|
+
if (showPopularGroup && visiblePopularModels.length > 0) {
|
|
90
|
+
groups.push({
|
|
91
|
+
provider: "popular",
|
|
92
|
+
label: "POPULAR",
|
|
93
|
+
options: visiblePopularModels,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
for (const option of filteredOptions) {
|
|
97
|
+
const provider = getModelCatalogProvider(option);
|
|
98
|
+
const label = formatProviderSectionLabel(provider);
|
|
99
|
+
const currentGroup = groups[groups.length - 1];
|
|
100
|
+
if (!currentGroup || currentGroup.provider !== provider) {
|
|
101
|
+
groups.push({ provider, label, options: [option] });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
currentGroup.options.push(option);
|
|
105
|
+
}
|
|
106
|
+
return groups;
|
|
107
|
+
}, [filteredOptions, popularModels, normalizedQuery]);
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const handleOutsidePointer = (event) => {
|
|
111
|
+
if (!rootRef.current?.contains(event.target)) {
|
|
112
|
+
setOpen(false);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
document.addEventListener("mousedown", handleOutsidePointer);
|
|
116
|
+
return () => document.removeEventListener("mousedown", handleOutsidePointer);
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
const handleSelect = (modelKey) => {
|
|
120
|
+
if (!modelKey) return;
|
|
121
|
+
onSelect(modelKey);
|
|
122
|
+
setQuery("");
|
|
123
|
+
setOpen(false);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const handleKeyDown = (event) => {
|
|
127
|
+
const firstVisibleOption = groupedOptions[0]?.options?.[0];
|
|
128
|
+
if (event.key === "Escape") {
|
|
129
|
+
setOpen(false);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (event.key === "Enter" && firstVisibleOption?.key) {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
handleSelect(firstVisibleOption.key);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return html`
|
|
139
|
+
<div class="relative" ref=${rootRef}>
|
|
140
|
+
<input
|
|
141
|
+
type="text"
|
|
142
|
+
value=${query}
|
|
143
|
+
placeholder=${placeholder}
|
|
144
|
+
onFocus=${() => setOpen(true)}
|
|
145
|
+
onInput=${(event) => {
|
|
146
|
+
setQuery(event.target.value);
|
|
147
|
+
setOpen(true);
|
|
148
|
+
}}
|
|
149
|
+
onKeyDown=${handleKeyDown}
|
|
150
|
+
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"
|
|
151
|
+
/>
|
|
152
|
+
${open
|
|
153
|
+
? html`
|
|
154
|
+
<div
|
|
155
|
+
class="absolute left-0 right-0 top-full mt-2 z-20 bg-modal border border-border rounded-xl shadow-2xl overflow-hidden"
|
|
156
|
+
>
|
|
157
|
+
<div class="max-h-80 overflow-y-auto">
|
|
158
|
+
${filteredOptions.length > 0
|
|
159
|
+
? groupedOptions.map(
|
|
160
|
+
(group, index) => html`
|
|
161
|
+
<div key=${group.provider}>
|
|
162
|
+
<div
|
|
163
|
+
class=${`sticky top-0 z-10 h-[22px] px-3 text-[12px] font-semibold tracking-wide text-gray-400 bg-[#151922] border-b border-border flex items-center ${
|
|
164
|
+
index > 0 ? "border-t border-border" : ""
|
|
165
|
+
}`}
|
|
166
|
+
>
|
|
167
|
+
${group.label}
|
|
168
|
+
</div>
|
|
169
|
+
${group.options.map(
|
|
170
|
+
(model) => html`
|
|
171
|
+
<button
|
|
172
|
+
key=${model.key}
|
|
173
|
+
type="button"
|
|
174
|
+
onMouseDown=${(event) => event.preventDefault()}
|
|
175
|
+
onClick=${() => handleSelect(model.key)}
|
|
176
|
+
class="w-full text-left px-3 py-2 hover:bg-white/5 border-b border-border last:border-b-0"
|
|
177
|
+
>
|
|
178
|
+
<div class="flex flex-col gap-1">
|
|
179
|
+
<div class="text-sm text-gray-200">
|
|
180
|
+
${model.label || model.key}
|
|
181
|
+
</div>
|
|
182
|
+
<div class="text-xs text-gray-500 font-mono">
|
|
183
|
+
${model.key}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</button>
|
|
187
|
+
`,
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
`,
|
|
191
|
+
)
|
|
192
|
+
: html`
|
|
193
|
+
<div class="px-3 py-3 text-xs text-gray-500">
|
|
194
|
+
No models match that search.
|
|
195
|
+
</div>
|
|
196
|
+
`}
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
`
|
|
200
|
+
: null}
|
|
201
|
+
</div>
|
|
202
|
+
`;
|
|
203
|
+
};
|
|
204
|
+
|
|
30
205
|
export const Models = ({ onRestartRequired = () => {} }) => {
|
|
31
206
|
const {
|
|
32
207
|
catalog,
|
|
@@ -52,26 +227,28 @@ export const Models = ({ onRestartRequired = () => {} }) => {
|
|
|
52
227
|
refreshCodexStatus,
|
|
53
228
|
} = useModels();
|
|
54
229
|
|
|
55
|
-
const [showAllModels, setShowAllModels] = useState(false);
|
|
56
|
-
|
|
57
230
|
const configuredKeys = useMemo(
|
|
58
231
|
() => new Set(Object.keys(configuredModels)),
|
|
59
232
|
[configuredModels],
|
|
60
233
|
);
|
|
61
234
|
|
|
62
235
|
const featuredModels = useMemo(() => getFeaturedModels(catalog), [catalog]);
|
|
236
|
+
const popularPickerModels = useMemo(
|
|
237
|
+
() => featuredModels.filter((model) => !configuredKeys.has(model.key)),
|
|
238
|
+
[featuredModels, configuredKeys],
|
|
239
|
+
);
|
|
63
240
|
|
|
64
241
|
const pickerModels = useMemo(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
242
|
+
return [...catalog]
|
|
243
|
+
.filter((model) => !configuredKeys.has(model.key))
|
|
244
|
+
.sort((a, b) => {
|
|
245
|
+
const providerCompare =
|
|
246
|
+
getProviderSortIndex(getModelCatalogProvider(a)) -
|
|
247
|
+
getProviderSortIndex(getModelCatalogProvider(b));
|
|
248
|
+
if (providerCompare !== 0) return providerCompare;
|
|
249
|
+
return String(a.label || a.key).localeCompare(String(b.label || b.key));
|
|
250
|
+
});
|
|
251
|
+
}, [catalog, configuredKeys]);
|
|
75
252
|
|
|
76
253
|
const requiredProviders = useMemo(
|
|
77
254
|
() => deriveRequiredProviders(configuredModels),
|
|
@@ -106,7 +283,7 @@ export const Models = ({ onRestartRequired = () => {} }) => {
|
|
|
106
283
|
() =>
|
|
107
284
|
Object.keys(configuredModels).map((key) => {
|
|
108
285
|
const catalogEntry = catalog.find((m) => m.key === key);
|
|
109
|
-
const provider =
|
|
286
|
+
const provider = getModelsTabAuthProvider(key);
|
|
110
287
|
const hasAuth = !!providerHasAuth[provider];
|
|
111
288
|
return {
|
|
112
289
|
key,
|
|
@@ -213,38 +390,16 @@ export const Models = ({ onRestartRequired = () => {} }) => {
|
|
|
213
390
|
</div>
|
|
214
391
|
`}
|
|
215
392
|
|
|
216
|
-
<div class="
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
e.target.value = "";
|
|
393
|
+
<div class="space-y-2">
|
|
394
|
+
<${SearchableModelPicker}
|
|
395
|
+
options=${pickerModels}
|
|
396
|
+
popularModels=${popularPickerModels}
|
|
397
|
+
placeholder="Add model..."
|
|
398
|
+
onSelect=${(modelKey) => {
|
|
399
|
+
addModel(modelKey);
|
|
400
|
+
if (!primary) setPrimaryModel(modelKey);
|
|
226
401
|
}}
|
|
227
|
-
|
|
228
|
-
>
|
|
229
|
-
<option value="">Select a model to add...</option>
|
|
230
|
-
${pickerModels.map(
|
|
231
|
-
(m) =>
|
|
232
|
-
html`<option value=${m.key}>${m.label || m.key}</option>`,
|
|
233
|
-
)}
|
|
234
|
-
</select>
|
|
235
|
-
${canToggleFullCatalog
|
|
236
|
-
? html`
|
|
237
|
-
<button
|
|
238
|
-
type="button"
|
|
239
|
-
onclick=${() => setShowAllModels((prev) => !prev)}
|
|
240
|
-
class="text-xs text-gray-500 hover:text-gray-300"
|
|
241
|
-
>
|
|
242
|
-
${showAllModels
|
|
243
|
-
? "Show recommended models"
|
|
244
|
-
: "Show full model catalog"}
|
|
245
|
-
</button>
|
|
246
|
-
`
|
|
247
|
-
: null}
|
|
402
|
+
/>
|
|
248
403
|
</div>
|
|
249
404
|
|
|
250
405
|
${loading
|
|
@@ -6,6 +6,10 @@ import { SecretInput } from "../secret-input.js";
|
|
|
6
6
|
import { ActionButton } from "../action-button.js";
|
|
7
7
|
import { exchangeCodexOAuth, disconnectCodex } from "../../lib/api.js";
|
|
8
8
|
import { showToast } from "../toast.js";
|
|
9
|
+
import {
|
|
10
|
+
kProviderAuthFields,
|
|
11
|
+
kProviderLabels,
|
|
12
|
+
} from "../../lib/model-config.js";
|
|
9
13
|
|
|
10
14
|
const html = htm.bind(h);
|
|
11
15
|
|
|
@@ -71,10 +75,24 @@ const kDefaultMode = {
|
|
|
71
75
|
field: "key",
|
|
72
76
|
};
|
|
73
77
|
|
|
78
|
+
const buildDefaultProviderModes = (provider) => {
|
|
79
|
+
const fields = kProviderAuthFields[provider] || [];
|
|
80
|
+
if (fields.length === 0) return [kDefaultMode];
|
|
81
|
+
return fields.map((fieldDef) => ({
|
|
82
|
+
id: "api_key",
|
|
83
|
+
label: fieldDef.label || "API Key",
|
|
84
|
+
profileSuffix: "default",
|
|
85
|
+
placeholder: fieldDef.placeholder || "...",
|
|
86
|
+
hint: fieldDef.hint,
|
|
87
|
+
url: fieldDef.url,
|
|
88
|
+
field: "key",
|
|
89
|
+
}));
|
|
90
|
+
};
|
|
91
|
+
|
|
74
92
|
const getProviderMeta = (provider) =>
|
|
75
93
|
kProviderMeta[provider] || {
|
|
76
|
-
label: provider,
|
|
77
|
-
modes:
|
|
94
|
+
label: kProviderLabels[provider] || provider,
|
|
95
|
+
modes: buildDefaultProviderModes(provider),
|
|
78
96
|
};
|
|
79
97
|
|
|
80
98
|
const resolveProfileId = (mode, provider) => {
|
|
@@ -137,7 +137,10 @@ export const Models = () => {
|
|
|
137
137
|
|
|
138
138
|
const setEnvValue = (key, value) => {
|
|
139
139
|
setEnvVars((prev) => {
|
|
140
|
-
const
|
|
140
|
+
const existing = prev.some((entry) => entry.key === key);
|
|
141
|
+
const next = existing
|
|
142
|
+
? prev.map((v) => (v.key === key ? { ...v, value } : v))
|
|
143
|
+
: [...prev, { key, value, editable: true }];
|
|
141
144
|
kModelsTabCache = { ...(kModelsTabCache || {}), envVars: next };
|
|
142
145
|
return next;
|
|
143
146
|
});
|
|
@@ -263,15 +266,11 @@ export const Models = () => {
|
|
|
263
266
|
(key) => getKeyVal(envVars, key) !== (savedAiValues[key] || ""),
|
|
264
267
|
);
|
|
265
268
|
const hasSelectedProviderAuth =
|
|
266
|
-
selectedModelProvider === "
|
|
267
|
-
? !!(getKeyVal(envVars, "ANTHROPIC_API_KEY") || getKeyVal(envVars, "ANTHROPIC_TOKEN"))
|
|
268
|
-
: selectedModelProvider === "openai"
|
|
269
|
-
? !!getKeyVal(envVars, "OPENAI_API_KEY")
|
|
270
|
-
: selectedModelProvider === "openai-codex"
|
|
269
|
+
selectedModelProvider === "openai-codex"
|
|
271
270
|
? !!codexStatus.connected
|
|
272
|
-
:
|
|
273
|
-
|
|
274
|
-
|
|
271
|
+
: (kProviderAuthFields[selectedAuthProvider] || []).some((field) =>
|
|
272
|
+
Boolean(getKeyVal(envVars, field.key)),
|
|
273
|
+
);
|
|
275
274
|
const canSaveChanges = !savingChanges && (aiCredentialsDirty || (modelDirty && hasSelectedProviderAuth));
|
|
276
275
|
|
|
277
276
|
const renderCredentialField = (field) => html`
|
|
@@ -3,6 +3,7 @@ import { useState } from "https://esm.sh/preact/hooks";
|
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import { ActionButton } from "../action-button.js";
|
|
5
5
|
import { LoadingSpinner } from "../loading-spinner.js";
|
|
6
|
+
import { buildApprovedImportSecrets } from "./welcome-secret-review-utils.js";
|
|
6
7
|
|
|
7
8
|
const html = htm.bind(h);
|
|
8
9
|
|
|
@@ -237,9 +238,10 @@ export const WelcomeImportStep = ({
|
|
|
237
238
|
<div
|
|
238
239
|
class="bg-yellow-900/20 border border-yellow-800/50 rounded-lg p-3 text-xs text-yellow-300"
|
|
239
240
|
>
|
|
240
|
-
AlphaClaw controls deployment env vars
|
|
241
|
+
AlphaClaw controls deployment tokens and env vars
|
|
241
242
|
(${(scanResult.managedEnvConflicts.vars || []).join(", ")}).
|
|
242
|
-
Imported values for these will be
|
|
243
|
+
Imported values for these will be overwritten with AlphaClaw-managed
|
|
244
|
+
env var references during import.
|
|
243
245
|
</div>
|
|
244
246
|
`
|
|
245
247
|
: null}
|
|
@@ -266,8 +268,7 @@ export const WelcomeImportStep = ({
|
|
|
266
268
|
>
|
|
267
269
|
<div>
|
|
268
270
|
<span class="text-xs text-cyan-300 font-medium">
|
|
269
|
-
${secretCount} possible secret${secretCount === 1 ? "" : "s"}
|
|
270
|
-
detected
|
|
271
|
+
${`${secretCount} possible secret${secretCount === 1 ? "" : "s"} detected`}
|
|
271
272
|
</span>
|
|
272
273
|
<p class="text-xs text-gray-500 mt-0.5">
|
|
273
274
|
Review and extract to environment variables
|
|
@@ -292,7 +293,7 @@ export const WelcomeImportStep = ({
|
|
|
292
293
|
className="w-full"
|
|
293
294
|
/>
|
|
294
295
|
<${ActionButton}
|
|
295
|
-
onClick=${() => onApprove(
|
|
296
|
+
onClick=${() => onApprove(buildApprovedImportSecrets(scanResult.secrets))}
|
|
296
297
|
loading=${scanning}
|
|
297
298
|
tone="primary"
|
|
298
299
|
size="md"
|
|
@@ -3,6 +3,7 @@ import { useState, useCallback } from "https://esm.sh/preact/hooks";
|
|
|
3
3
|
import htm from "https://esm.sh/htm";
|
|
4
4
|
import { ActionButton } from "../action-button.js";
|
|
5
5
|
import { LoadingSpinner } from "../loading-spinner.js";
|
|
6
|
+
import { buildApprovedImportSecrets } from "./welcome-secret-review-utils.js";
|
|
6
7
|
|
|
7
8
|
const html = htm.bind(h);
|
|
8
9
|
|
|
@@ -116,13 +117,16 @@ export const WelcomeSecretReviewStep = ({
|
|
|
116
117
|
).length;
|
|
117
118
|
|
|
118
119
|
const handleExtract = () => {
|
|
119
|
-
const approved =
|
|
120
|
-
.
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
const approved = buildApprovedImportSecrets(
|
|
121
|
+
secrets.map((secret) => ({
|
|
122
|
+
...secret,
|
|
123
|
+
confidence: selections[secret.configPath]?.selected
|
|
124
|
+
? "high"
|
|
125
|
+
: "medium",
|
|
123
126
|
suggestedEnvVar:
|
|
124
|
-
selections[
|
|
125
|
-
}))
|
|
127
|
+
selections[secret.configPath]?.envVarName || secret.suggestedEnvVar,
|
|
128
|
+
})),
|
|
129
|
+
);
|
|
126
130
|
onApprove(approved);
|
|
127
131
|
};
|
|
128
132
|
|
|
@@ -174,12 +178,14 @@ export const WelcomeSecretReviewStep = ({
|
|
|
174
178
|
<${ActionButton}
|
|
175
179
|
onClick=${onBack}
|
|
176
180
|
tone="secondary"
|
|
181
|
+
size="md"
|
|
177
182
|
idleLabel="Back"
|
|
178
183
|
className="w-full"
|
|
179
184
|
/>
|
|
180
185
|
<${ActionButton}
|
|
181
186
|
onClick=${handleExtract}
|
|
182
187
|
tone="primary"
|
|
188
|
+
size="md"
|
|
183
189
|
idleLabel=${selectedCount > 0
|
|
184
190
|
? `Extract ${selectedCount} Secret${selectedCount === 1 ? "" : "s"}`
|
|
185
191
|
: "Skip All"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const buildApprovedImportSecrets = (secrets = []) =>
|
|
2
|
+
(Array.isArray(secrets) ? secrets : [])
|
|
3
|
+
.filter((secret) => secret?.confidence === "high")
|
|
4
|
+
.map((secret) => ({
|
|
5
|
+
...secret,
|
|
6
|
+
suggestedEnvVar: secret?.suggestedEnvVar || "",
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
export const buildApprovedImportVals = (approvedSecrets = []) =>
|
|
10
|
+
(Array.isArray(approvedSecrets) ? approvedSecrets : []).reduce(
|
|
11
|
+
(nextVals, secret) => {
|
|
12
|
+
const envVar = String(secret?.suggestedEnvVar || "").trim();
|
|
13
|
+
const value = String(secret?.value || "");
|
|
14
|
+
if (!envVar || !value) return nextVals;
|
|
15
|
+
nextVals[envVar] = value;
|
|
16
|
+
return nextVals;
|
|
17
|
+
},
|
|
18
|
+
{},
|
|
19
|
+
);
|
|
@@ -189,7 +189,10 @@ export const Providers = ({ onRestartRequired = () => {} }) => {
|
|
|
189
189
|
|
|
190
190
|
const setEnvValue = (key, value) => {
|
|
191
191
|
setEnvVars((prev) => {
|
|
192
|
-
const
|
|
192
|
+
const existing = prev.some((entry) => entry.key === key);
|
|
193
|
+
const next = existing
|
|
194
|
+
? prev.map((v) => (v.key === key ? { ...v, value } : v))
|
|
195
|
+
: [...prev, { key, value, editable: true }];
|
|
193
196
|
kProvidersTabCache = { ...(kProvidersTabCache || {}), envVars: next };
|
|
194
197
|
return next;
|
|
195
198
|
});
|
|
@@ -224,18 +227,11 @@ export const Providers = ({ onRestartRequired = () => {} }) => {
|
|
|
224
227
|
(key) => getKeyVal(envVars, key) !== (savedAiValues[key] || ""),
|
|
225
228
|
);
|
|
226
229
|
const hasSelectedProviderAuth =
|
|
227
|
-
selectedModelProvider === "
|
|
228
|
-
? !!
|
|
229
|
-
|
|
230
|
-
getKeyVal(envVars,
|
|
231
|
-
)
|
|
232
|
-
: selectedModelProvider === "openai"
|
|
233
|
-
? !!getKeyVal(envVars, "OPENAI_API_KEY")
|
|
234
|
-
: selectedModelProvider === "openai-codex"
|
|
235
|
-
? !!codexStatus.connected
|
|
236
|
-
: selectedModelProvider === "google"
|
|
237
|
-
? !!getKeyVal(envVars, "GEMINI_API_KEY")
|
|
238
|
-
: false;
|
|
230
|
+
selectedModelProvider === "openai-codex"
|
|
231
|
+
? !!codexStatus.connected
|
|
232
|
+
: (kProviderAuthFields[selectedAuthProvider] || []).some((field) =>
|
|
233
|
+
Boolean(getKeyVal(envVars, field.key)),
|
|
234
|
+
);
|
|
239
235
|
const canSaveChanges =
|
|
240
236
|
!savingChanges &&
|
|
241
237
|
(aiCredentialsDirty || (modelDirty && hasSelectedProviderAuth));
|
|
@@ -31,8 +31,9 @@ const getCacheHitRateValueClass = (ratio) => {
|
|
|
31
31
|
const getOverviewMetrics = (summary) => {
|
|
32
32
|
const totals = summary?.totals || {};
|
|
33
33
|
const cacheReadTokens = Number(totals.cacheReadTokens || 0);
|
|
34
|
+
const cacheWriteTokens = Number(totals.cacheWriteTokens || 0);
|
|
34
35
|
const inputTokens = Number(totals.inputTokens || 0);
|
|
35
|
-
const promptTokens = inputTokens + cacheReadTokens;
|
|
36
|
+
const promptTokens = inputTokens + cacheReadTokens + cacheWriteTokens;
|
|
36
37
|
const turnCount = Number(totals.turnCount || 0);
|
|
37
38
|
const totalTokens = Number(totals.totalTokens || 0);
|
|
38
39
|
const totalCost = Number(totals.totalCost || 0);
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from "../onboarding/use-welcome-storage.js";
|
|
30
30
|
import { useWelcomeCodex } from "../onboarding/use-welcome-codex.js";
|
|
31
31
|
import { useWelcomePairing } from "../onboarding/use-welcome-pairing.js";
|
|
32
|
+
import { buildApprovedImportVals } from "../onboarding/welcome-secret-review-utils.js";
|
|
32
33
|
|
|
33
34
|
const kMaxOnboardingVars = 64;
|
|
34
35
|
const kMaxEnvKeyLength = 128;
|
|
@@ -413,6 +414,7 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
413
414
|
setImportError(null);
|
|
414
415
|
try {
|
|
415
416
|
const skipSecretExtraction = approvedSecrets.length === 0;
|
|
417
|
+
const approvedImportVals = buildApprovedImportVals(approvedSecrets);
|
|
416
418
|
const result = await applyImport({
|
|
417
419
|
tempDir: importTempDir,
|
|
418
420
|
approvedSecrets,
|
|
@@ -430,6 +432,7 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
430
432
|
);
|
|
431
433
|
setVals((prev) => ({
|
|
432
434
|
...prev,
|
|
435
|
+
...approvedImportVals,
|
|
433
436
|
...(result.preFill || {}),
|
|
434
437
|
[kImportPlaceholderReviewKey]: nextPlaceholderReview,
|
|
435
438
|
[kImportPlaceholderSkipConfirmedKey]: false,
|