@chrysb/alphaclaw 0.9.15 → 0.9.17
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/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +2168 -2103
- package/lib/public/js/components/agents-tab/agent-overview/model-card.js +59 -7
- package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +124 -0
- package/lib/public/js/components/envars.js +1 -1
- package/lib/public/js/components/file-tree.js +82 -6
- package/lib/public/js/components/row-accessory-select.js +52 -0
- package/lib/public/js/lib/api.js +11 -1
- package/lib/public/js/lib/model-catalog.js +6 -0
- package/lib/public/js/lib/model-config.js +12 -7
- package/lib/public/js/lib/thinking-levels.js +37 -0
- package/lib/server/agents/agents.js +33 -7
- package/lib/server/agents/channels.js +4 -2
- package/lib/server/chat-ws.js +4 -1
- package/lib/server/constants.js +25 -0
- package/lib/server/cost-utils.js +2 -0
- package/lib/server/db/auth/index.js +147 -0
- package/lib/server/db/auth/schema.js +17 -0
- package/lib/server/gateway.js +158 -19
- package/lib/server/helpers.js +1 -3
- package/lib/server/init/register-server-routes.js +37 -18
- package/lib/server/init/runtime-init.js +4 -0
- package/lib/server/init/server-lifecycle.js +1 -24
- package/lib/server/login-throttle.js +242 -60
- package/lib/server/model-catalog-bootstrap.json +5 -0
- package/lib/server/onboarding/index.js +2 -2
- package/lib/server/openclaw-thinking.js +103 -0
- package/lib/server/openclaw-version.js +1 -1
- package/lib/server/routes/agents.js +10 -3
- package/lib/server/routes/browse/constants.js +3 -1
- package/lib/server/routes/browse/index.js +39 -11
- package/lib/server/routes/models.js +35 -1
- package/lib/server/routes/onboarding.js +2 -2
- package/lib/server/routes/system.js +2 -2
- package/lib/server/usage-tracker-config.js +52 -1
- package/lib/server.js +26 -22
- package/package.json +2 -2
|
@@ -3,6 +3,7 @@ import htm from "htm";
|
|
|
3
3
|
import { Badge } from "../../badge.js";
|
|
4
4
|
import { LoadingSpinner } from "../../loading-spinner.js";
|
|
5
5
|
import { OverflowMenu, OverflowMenuItem } from "../../overflow-menu.js";
|
|
6
|
+
import { RowAccessorySelect } from "../../row-accessory-select.js";
|
|
6
7
|
import {
|
|
7
8
|
getModelDisplayLabel,
|
|
8
9
|
SearchableModelPicker,
|
|
@@ -22,16 +23,25 @@ export const AgentModelCard = ({
|
|
|
22
23
|
canEditModel,
|
|
23
24
|
effectiveModel,
|
|
24
25
|
effectiveModelEntry,
|
|
26
|
+
formatInheritedThinkingLabel,
|
|
25
27
|
handleClearModelOverride,
|
|
26
28
|
handleSelectModel,
|
|
29
|
+
handleSelectThinkingDefault,
|
|
27
30
|
hasDistinctModelOverride,
|
|
31
|
+
hasDistinctThinkingOverride,
|
|
32
|
+
inheritedThinkingDefault,
|
|
28
33
|
loading,
|
|
29
34
|
menuOpen,
|
|
30
35
|
modelEntries,
|
|
31
36
|
popularModels,
|
|
32
37
|
remainingModelOptions,
|
|
33
38
|
setMenuOpen,
|
|
39
|
+
showThinkingSelect,
|
|
40
|
+
thinkingOptionsLoading,
|
|
41
|
+
thinkingSelectOptions,
|
|
42
|
+
thinkingSelectValue,
|
|
34
43
|
updatingModel,
|
|
44
|
+
updatingThinking,
|
|
35
45
|
} = useModelCard({
|
|
36
46
|
agent,
|
|
37
47
|
onUpdateAgent,
|
|
@@ -63,7 +73,19 @@ export const AgentModelCard = ({
|
|
|
63
73
|
handleClearModelOverride();
|
|
64
74
|
}}
|
|
65
75
|
>
|
|
66
|
-
Inherit from defaults
|
|
76
|
+
Inherit model from defaults
|
|
77
|
+
</${OverflowMenuItem}>
|
|
78
|
+
`
|
|
79
|
+
: null}
|
|
80
|
+
${hasDistinctThinkingOverride
|
|
81
|
+
? html`
|
|
82
|
+
<${OverflowMenuItem}
|
|
83
|
+
onClick=${() => {
|
|
84
|
+
setMenuOpen(false);
|
|
85
|
+
handleSelectThinkingDefault("");
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
Inherit thinking from defaults
|
|
67
89
|
</${OverflowMenuItem}>
|
|
68
90
|
`
|
|
69
91
|
: null}
|
|
@@ -93,17 +115,20 @@ export const AgentModelCard = ({
|
|
|
93
115
|
</p>`
|
|
94
116
|
: html`
|
|
95
117
|
<div class="space-y-1">
|
|
96
|
-
${modelEntries.map(
|
|
97
|
-
|
|
118
|
+
${modelEntries.map((entry) => {
|
|
119
|
+
const isPrimary = entry.key === effectiveModel;
|
|
120
|
+
const showThinkingPicker =
|
|
121
|
+
isPrimary && showThinkingSelect && !thinkingOptionsLoading;
|
|
122
|
+
return html`
|
|
98
123
|
<div
|
|
99
124
|
key=${entry.key}
|
|
100
|
-
class="flex items-center justify-between py-1"
|
|
125
|
+
class="flex items-center justify-between gap-3 py-1"
|
|
101
126
|
>
|
|
102
127
|
<div class="flex items-center gap-2 min-w-0">
|
|
103
128
|
<span class="text-sm text-body truncate">
|
|
104
129
|
${getModelDisplayLabel(entry)}
|
|
105
130
|
</span>
|
|
106
|
-
${
|
|
131
|
+
${isPrimary
|
|
107
132
|
? html`<${Badge} tone="cyan">Primary</${Badge}>`
|
|
108
133
|
: html`
|
|
109
134
|
<button
|
|
@@ -115,9 +140,36 @@ export const AgentModelCard = ({
|
|
|
115
140
|
</button>
|
|
116
141
|
`}
|
|
117
142
|
</div>
|
|
143
|
+
${showThinkingPicker
|
|
144
|
+
? html`
|
|
145
|
+
<${RowAccessorySelect}
|
|
146
|
+
ariaLabel="Agent thinking level"
|
|
147
|
+
title="Agent thinking level"
|
|
148
|
+
value=${thinkingSelectValue}
|
|
149
|
+
disabled=${saving ||
|
|
150
|
+
updatingModel ||
|
|
151
|
+
updatingThinking ||
|
|
152
|
+
!canEditModel}
|
|
153
|
+
onChange=${handleSelectThinkingDefault}
|
|
154
|
+
>
|
|
155
|
+
<option value="">
|
|
156
|
+
${formatInheritedThinkingLabel(
|
|
157
|
+
inheritedThinkingDefault,
|
|
158
|
+
)}
|
|
159
|
+
</option>
|
|
160
|
+
${thinkingSelectOptions.map(
|
|
161
|
+
(option) => html`
|
|
162
|
+
<option value=${option.value}>
|
|
163
|
+
${option.label}
|
|
164
|
+
</option>
|
|
165
|
+
`,
|
|
166
|
+
)}
|
|
167
|
+
</${RowAccessorySelect}>
|
|
168
|
+
`
|
|
169
|
+
: null}
|
|
118
170
|
</div>
|
|
119
|
-
|
|
120
|
-
)}
|
|
171
|
+
`;
|
|
172
|
+
})}
|
|
121
173
|
</div>
|
|
122
174
|
`}
|
|
123
175
|
${loading
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "preact/hooks";
|
|
2
|
+
import { fetchThinkingOptions } from "../../../lib/api.js";
|
|
3
|
+
import {
|
|
4
|
+
formatInheritedThinkingLabel,
|
|
5
|
+
formatThinkingLevelLabel,
|
|
6
|
+
shouldShowThinkingLevelSelect,
|
|
7
|
+
} from "../../../lib/thinking-levels.js";
|
|
2
8
|
import { useModels } from "../../models-tab/use-models.js";
|
|
3
9
|
import {
|
|
4
10
|
buildProviderHasAuth,
|
|
@@ -25,7 +31,14 @@ export const useModelCard = ({
|
|
|
25
31
|
onUpdateAgent = async () => {},
|
|
26
32
|
}) => {
|
|
27
33
|
const [updatingModel, setUpdatingModel] = useState(false);
|
|
34
|
+
const [updatingThinking, setUpdatingThinking] = useState(false);
|
|
28
35
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
36
|
+
const [thinkingOptions, setThinkingOptions] = useState({
|
|
37
|
+
levels: [],
|
|
38
|
+
inheritedDefault: "off",
|
|
39
|
+
modelDefault: "off",
|
|
40
|
+
});
|
|
41
|
+
const [thinkingOptionsLoading, setThinkingOptionsLoading] = useState(false);
|
|
29
42
|
const {
|
|
30
43
|
catalog,
|
|
31
44
|
primary: defaultPrimaryModel,
|
|
@@ -41,6 +54,45 @@ export const useModelCard = ({
|
|
|
41
54
|
const hasDistinctModelOverride =
|
|
42
55
|
!!explicitModel &&
|
|
43
56
|
String(explicitModel).trim() !== String(defaultPrimaryModel || "").trim();
|
|
57
|
+
const explicitThinkingDefault = String(agent.thinkingDefault || "").trim();
|
|
58
|
+
const inheritedThinkingDefault = String(
|
|
59
|
+
thinkingOptions.inheritedDefault || thinkingOptions.modelDefault || "off",
|
|
60
|
+
).trim();
|
|
61
|
+
const hasDistinctThinkingOverride =
|
|
62
|
+
!!explicitThinkingDefault &&
|
|
63
|
+
explicitThinkingDefault !== inheritedThinkingDefault;
|
|
64
|
+
const showThinkingSelect = shouldShowThinkingLevelSelect(
|
|
65
|
+
thinkingOptions.levels,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const modelKey = String(effectiveModel || "").trim();
|
|
70
|
+
if (!modelKey.includes("/")) {
|
|
71
|
+
setThinkingOptions({
|
|
72
|
+
levels: [],
|
|
73
|
+
inheritedDefault: "off",
|
|
74
|
+
modelDefault: "off",
|
|
75
|
+
});
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
let cancelled = false;
|
|
79
|
+
setThinkingOptionsLoading(true);
|
|
80
|
+
fetchThinkingOptions(modelKey)
|
|
81
|
+
.then((payload) => {
|
|
82
|
+
if (cancelled || !payload?.ok) return;
|
|
83
|
+
setThinkingOptions({
|
|
84
|
+
levels: Array.isArray(payload.levels) ? payload.levels : [],
|
|
85
|
+
inheritedDefault: String(payload.inheritedDefault || "off").trim(),
|
|
86
|
+
modelDefault: String(payload.modelDefault || "off").trim(),
|
|
87
|
+
});
|
|
88
|
+
})
|
|
89
|
+
.finally(() => {
|
|
90
|
+
if (!cancelled) setThinkingOptionsLoading(false);
|
|
91
|
+
});
|
|
92
|
+
return () => {
|
|
93
|
+
cancelled = true;
|
|
94
|
+
};
|
|
95
|
+
}, [effectiveModel]);
|
|
44
96
|
|
|
45
97
|
const providerHasAuth = useMemo(
|
|
46
98
|
() => buildProviderHasAuth({ authProfiles, codexStatus }),
|
|
@@ -149,6 +201,69 @@ export const useModelCard = ({
|
|
|
149
201
|
}
|
|
150
202
|
};
|
|
151
203
|
|
|
204
|
+
const handleSelectThinkingDefault = async (nextValue) => {
|
|
205
|
+
const normalizedValue = String(nextValue || "").trim();
|
|
206
|
+
const isInherit = !normalizedValue;
|
|
207
|
+
if (isInherit) {
|
|
208
|
+
if (!hasDistinctThinkingOverride) return;
|
|
209
|
+
setUpdatingThinking(true);
|
|
210
|
+
try {
|
|
211
|
+
await onUpdateAgent(
|
|
212
|
+
String(agent.id || "").trim(),
|
|
213
|
+
{ thinkingDefault: null },
|
|
214
|
+
"Agent thinking level reset to default",
|
|
215
|
+
);
|
|
216
|
+
} finally {
|
|
217
|
+
setUpdatingThinking(false);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (normalizedValue === explicitThinkingDefault) return;
|
|
222
|
+
setUpdatingThinking(true);
|
|
223
|
+
try {
|
|
224
|
+
await onUpdateAgent(
|
|
225
|
+
String(agent.id || "").trim(),
|
|
226
|
+
{ thinkingDefault: normalizedValue },
|
|
227
|
+
"Agent thinking level updated",
|
|
228
|
+
);
|
|
229
|
+
} finally {
|
|
230
|
+
setUpdatingThinking(false);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const thinkingSelectValue = hasDistinctThinkingOverride
|
|
235
|
+
? explicitThinkingDefault
|
|
236
|
+
: "";
|
|
237
|
+
const thinkingSelectOptions = useMemo(() => {
|
|
238
|
+
const seen = new Set();
|
|
239
|
+
const options = [];
|
|
240
|
+
const addOption = (value, label) => {
|
|
241
|
+
const normalizedValue = String(value || "").trim();
|
|
242
|
+
if (!normalizedValue || seen.has(normalizedValue)) return;
|
|
243
|
+
seen.add(normalizedValue);
|
|
244
|
+
options.push({
|
|
245
|
+
value: normalizedValue,
|
|
246
|
+
label: String(label || formatThinkingLevelLabel(normalizedValue)).trim(),
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
for (const entry of thinkingOptions.levels) {
|
|
250
|
+
addOption(
|
|
251
|
+
entry?.id,
|
|
252
|
+
formatThinkingLevelLabel(entry?.label || entry?.id),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (
|
|
256
|
+
explicitThinkingDefault &&
|
|
257
|
+
!seen.has(explicitThinkingDefault)
|
|
258
|
+
) {
|
|
259
|
+
addOption(
|
|
260
|
+
explicitThinkingDefault,
|
|
261
|
+
`${formatThinkingLevelLabel(explicitThinkingDefault)} (custom)`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return options;
|
|
265
|
+
}, [explicitThinkingDefault, thinkingOptions.levels]);
|
|
266
|
+
|
|
152
267
|
return {
|
|
153
268
|
authorizedModelOptions,
|
|
154
269
|
canEditModel: modelsReady && !loadingModels,
|
|
@@ -156,13 +271,22 @@ export const useModelCard = ({
|
|
|
156
271
|
effectiveModelEntry,
|
|
157
272
|
handleClearModelOverride,
|
|
158
273
|
handleSelectModel,
|
|
274
|
+
handleSelectThinkingDefault,
|
|
159
275
|
hasDistinctModelOverride,
|
|
276
|
+
hasDistinctThinkingOverride,
|
|
277
|
+
inheritedThinkingDefault,
|
|
160
278
|
loading: !modelsReady || loadingModels,
|
|
161
279
|
menuOpen,
|
|
162
280
|
modelEntries,
|
|
163
281
|
popularModels,
|
|
164
282
|
remainingModelOptions,
|
|
165
283
|
setMenuOpen,
|
|
284
|
+
showThinkingSelect,
|
|
285
|
+
thinkingOptionsLoading,
|
|
286
|
+
thinkingSelectOptions,
|
|
287
|
+
thinkingSelectValue,
|
|
288
|
+
formatInheritedThinkingLabel,
|
|
166
289
|
updatingModel,
|
|
290
|
+
updatingThinking,
|
|
167
291
|
};
|
|
168
292
|
};
|
|
@@ -58,7 +58,7 @@ const normalizeEnvVarKey = (raw) =>
|
|
|
58
58
|
.toUpperCase()
|
|
59
59
|
.replace(/[^A-Z0-9_]/g, "_");
|
|
60
60
|
const kManagedChannelTokenPattern =
|
|
61
|
-
/^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN)(?:_[A-Z0-9_]+)?$/;
|
|
61
|
+
/^(?:TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN|SLACK_BOT_TOKEN|SLACK_APP_TOKEN|WHATSAPP_OWNER_NUMBER)(?:_[A-Z0-9_]+)?$/;
|
|
62
62
|
const stripSurroundingQuotes = (raw) => {
|
|
63
63
|
const value = String(raw || "").trim();
|
|
64
64
|
if (value.length < 2) return value;
|
|
@@ -76,6 +76,16 @@ const collectFolderPaths = (node, folderPaths) => {
|
|
|
76
76
|
);
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const collectTruncatedExpandedFolderPaths = (node, expandedPaths, folderPaths) => {
|
|
80
|
+
if (!node || node.type !== "folder") return;
|
|
81
|
+
if (node.truncated && expandedPaths.has(node.path || "")) {
|
|
82
|
+
folderPaths.push(node.path || "");
|
|
83
|
+
}
|
|
84
|
+
(node.children || []).forEach((childNode) =>
|
|
85
|
+
collectTruncatedExpandedFolderPaths(childNode, expandedPaths, folderPaths),
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
79
89
|
const collectFilePaths = (node, filePaths) => {
|
|
80
90
|
if (!node) return;
|
|
81
91
|
if (node.type === "file") {
|
|
@@ -104,6 +114,19 @@ const removeTreePath = (node, targetPath) => {
|
|
|
104
114
|
};
|
|
105
115
|
};
|
|
106
116
|
|
|
117
|
+
const replaceTreeNode = (node, nextNode) => {
|
|
118
|
+
if (!node || !nextNode) return node;
|
|
119
|
+
if (String(node.path || "") === String(nextNode.path || "")) return nextNode;
|
|
120
|
+
if (node.type !== "folder") return node;
|
|
121
|
+
const nextChildren = (node.children || []).map((childNode) =>
|
|
122
|
+
replaceTreeNode(childNode, nextNode),
|
|
123
|
+
);
|
|
124
|
+
return {
|
|
125
|
+
...node,
|
|
126
|
+
children: nextChildren,
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
107
130
|
const filterTreeNode = (node, normalizedQuery) => {
|
|
108
131
|
if (!node) return null;
|
|
109
132
|
const query = String(normalizedQuery || "")
|
|
@@ -410,6 +433,7 @@ const TreeNode = ({
|
|
|
410
433
|
onCreationConfirm,
|
|
411
434
|
onCreationCancel,
|
|
412
435
|
dragSourcePath = "",
|
|
436
|
+
loadingFolderPaths = new Set(),
|
|
413
437
|
}) => {
|
|
414
438
|
if (!node) return null;
|
|
415
439
|
if (node.type === "file") {
|
|
@@ -471,6 +495,7 @@ const TreeNode = ({
|
|
|
471
495
|
|
|
472
496
|
const folderPath = node.path || "";
|
|
473
497
|
const isCollapsed = isSearchActive ? false : !expandedPaths.has(folderPath);
|
|
498
|
+
const isLoadingFolder = loadingFolderPaths.has(folderPath);
|
|
474
499
|
const isFolderActive = selectedPath === folderPath;
|
|
475
500
|
const isFolderLocked = folderPath && matchesBrowsePolicyPath(
|
|
476
501
|
kLockedBrowsePaths,
|
|
@@ -484,7 +509,7 @@ const TreeNode = ({
|
|
|
484
509
|
class=${`tree-folder ${isCollapsed ? "collapsed" : ""} ${isFolderActive ? "active" : ""} ${isDropTarget ? "is-drop-target" : ""}`.trim()}
|
|
485
510
|
onclick=${() => {
|
|
486
511
|
if (!folderPath) return;
|
|
487
|
-
onSetFolderExpanded(folderPath, isCollapsed);
|
|
512
|
+
onSetFolderExpanded(folderPath, isCollapsed, node);
|
|
488
513
|
onSelectFolder(folderPath);
|
|
489
514
|
}}
|
|
490
515
|
oncontextmenu=${(e) => {
|
|
@@ -540,8 +565,9 @@ const TreeNode = ({
|
|
|
540
565
|
event.preventDefault();
|
|
541
566
|
event.stopPropagation();
|
|
542
567
|
if (!folderPath) return;
|
|
543
|
-
onSetFolderExpanded(folderPath, isCollapsed);
|
|
568
|
+
onSetFolderExpanded(folderPath, isCollapsed, node);
|
|
544
569
|
}}
|
|
570
|
+
disabled=${isLoadingFolder}
|
|
545
571
|
>
|
|
546
572
|
<span class="arrow">▼</span>
|
|
547
573
|
</button>
|
|
@@ -587,6 +613,7 @@ const TreeNode = ({
|
|
|
587
613
|
onCreationConfirm=${onCreationConfirm}
|
|
588
614
|
onCreationCancel=${onCreationCancel}
|
|
589
615
|
dragSourcePath=${dragSourcePath}
|
|
616
|
+
loadingFolderPaths=${loadingFolderPaths}
|
|
590
617
|
/>
|
|
591
618
|
`,
|
|
592
619
|
)}
|
|
@@ -623,6 +650,7 @@ const TreeNode = ({
|
|
|
623
650
|
onCreationConfirm=${onCreationConfirm}
|
|
624
651
|
onCreationCancel=${onCreationCancel}
|
|
625
652
|
dragSourcePath=${dragSourcePath}
|
|
653
|
+
loadingFolderPaths=${loadingFolderPaths}
|
|
626
654
|
/>
|
|
627
655
|
`,
|
|
628
656
|
)}
|
|
@@ -650,6 +678,7 @@ export const FileTree = ({
|
|
|
650
678
|
const [creatingType, setCreatingType] = useState("");
|
|
651
679
|
const [contextMenu, setContextMenu] = useState(null);
|
|
652
680
|
const [dragSourcePath, setDragSourcePath] = useState("");
|
|
681
|
+
const [loadingFolderPaths, setLoadingFolderPaths] = useState(new Set());
|
|
653
682
|
const [selectedFolder, setSelectedFolder] = useState("");
|
|
654
683
|
const effectiveSelectedPath = selectedFolder || selectedPath;
|
|
655
684
|
const searchInputRef = useRef(null);
|
|
@@ -661,10 +690,31 @@ export const FileTree = ({
|
|
|
661
690
|
try {
|
|
662
691
|
const data = await fetchBrowseTree();
|
|
663
692
|
const nextRoot = data.root || null;
|
|
664
|
-
const
|
|
693
|
+
const nextExpandedPaths =
|
|
694
|
+
expandedPaths instanceof Set ? expandedPaths : new Set();
|
|
695
|
+
let hydratedRoot = nextRoot;
|
|
696
|
+
const hydratedPaths = new Set();
|
|
697
|
+
while (true) {
|
|
698
|
+
const truncatedExpandedPaths = [];
|
|
699
|
+
collectTruncatedExpandedFolderPaths(
|
|
700
|
+
hydratedRoot,
|
|
701
|
+
nextExpandedPaths,
|
|
702
|
+
truncatedExpandedPaths,
|
|
703
|
+
);
|
|
704
|
+
const nextFolderPath = truncatedExpandedPaths.find(
|
|
705
|
+
(folderPath) => !hydratedPaths.has(folderPath),
|
|
706
|
+
);
|
|
707
|
+
if (!nextFolderPath) break;
|
|
708
|
+
hydratedPaths.add(nextFolderPath);
|
|
709
|
+
const subtreeData = await fetchBrowseTree({ path: nextFolderPath });
|
|
710
|
+
if (subtreeData.root) {
|
|
711
|
+
hydratedRoot = replaceTreeNode(hydratedRoot, subtreeData.root);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const nextSignature = JSON.stringify(hydratedRoot || {});
|
|
665
715
|
if (treeSignatureRef.current !== nextSignature) {
|
|
666
716
|
treeSignatureRef.current = nextSignature;
|
|
667
|
-
setTreeRoot(
|
|
717
|
+
setTreeRoot(hydratedRoot);
|
|
668
718
|
}
|
|
669
719
|
setExpandedPaths((previousPaths) =>
|
|
670
720
|
previousPaths instanceof Set ? previousPaths : new Set(),
|
|
@@ -677,7 +727,7 @@ export const FileTree = ({
|
|
|
677
727
|
} finally {
|
|
678
728
|
if (showLoading) setLoading(false);
|
|
679
729
|
}
|
|
680
|
-
}, []);
|
|
730
|
+
}, [expandedPaths]);
|
|
681
731
|
|
|
682
732
|
useEffect(() => {
|
|
683
733
|
loadTree({ showLoading: true });
|
|
@@ -834,7 +884,31 @@ export const FileTree = ({
|
|
|
834
884
|
onPreviewFile("");
|
|
835
885
|
}, [isSearchActive, filteredFilePaths, searchActivePath, onPreviewFile]);
|
|
836
886
|
|
|
837
|
-
const setFolderExpanded = (folderPath, nextExpanded) => {
|
|
887
|
+
const setFolderExpanded = async (folderPath, nextExpanded, node = null) => {
|
|
888
|
+
if (nextExpanded === true && node?.truncated) {
|
|
889
|
+
setLoadingFolderPaths((previousPaths) => {
|
|
890
|
+
const nextPaths =
|
|
891
|
+
previousPaths instanceof Set ? new Set(previousPaths) : new Set();
|
|
892
|
+
nextPaths.add(folderPath);
|
|
893
|
+
return nextPaths;
|
|
894
|
+
});
|
|
895
|
+
try {
|
|
896
|
+
const data = await fetchBrowseTree({ path: folderPath });
|
|
897
|
+
if (data.root) {
|
|
898
|
+
setTreeRoot((previousRoot) => replaceTreeNode(previousRoot, data.root));
|
|
899
|
+
}
|
|
900
|
+
} catch (loadError) {
|
|
901
|
+
showToast(loadError.message || "Could not load folder", "error");
|
|
902
|
+
return;
|
|
903
|
+
} finally {
|
|
904
|
+
setLoadingFolderPaths((previousPaths) => {
|
|
905
|
+
const nextPaths =
|
|
906
|
+
previousPaths instanceof Set ? new Set(previousPaths) : new Set();
|
|
907
|
+
nextPaths.delete(folderPath);
|
|
908
|
+
return nextPaths;
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
}
|
|
838
912
|
setExpandedPaths((previousPaths) => {
|
|
839
913
|
const nextPaths =
|
|
840
914
|
previousPaths instanceof Set ? new Set(previousPaths) : new Set();
|
|
@@ -1210,6 +1284,7 @@ export const FileTree = ({
|
|
|
1210
1284
|
onCreationConfirm=${confirmCreate}
|
|
1211
1285
|
onCreationCancel=${cancelCreate}
|
|
1212
1286
|
dragSourcePath=${dragSourcePath}
|
|
1287
|
+
loadingFolderPaths=${loadingFolderPaths}
|
|
1213
1288
|
/>
|
|
1214
1289
|
`,
|
|
1215
1290
|
)}
|
|
@@ -1245,6 +1320,7 @@ export const FileTree = ({
|
|
|
1245
1320
|
onCreationConfirm=${confirmCreate}
|
|
1246
1321
|
onCreationCancel=${cancelCreate}
|
|
1247
1322
|
dragSourcePath=${dragSourcePath}
|
|
1323
|
+
loadingFolderPaths=${loadingFolderPaths}
|
|
1248
1324
|
/>
|
|
1249
1325
|
`,
|
|
1250
1326
|
)}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
import htm from "htm";
|
|
3
|
+
|
|
4
|
+
const html = htm.bind(h);
|
|
5
|
+
|
|
6
|
+
const RowAccessoryChevron = () => html`
|
|
7
|
+
<svg
|
|
8
|
+
width="14"
|
|
9
|
+
height="14"
|
|
10
|
+
viewBox="0 0 16 16"
|
|
11
|
+
fill="none"
|
|
12
|
+
class="text-fg-dim"
|
|
13
|
+
aria-hidden="true"
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
d="M3.5 6L8 10.5L12.5 6"
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
stroke-width="2"
|
|
19
|
+
stroke-linecap="round"
|
|
20
|
+
stroke-linejoin="round"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
export const RowAccessorySelect = ({
|
|
26
|
+
ariaLabel = "",
|
|
27
|
+
title = "",
|
|
28
|
+
value = "",
|
|
29
|
+
disabled = false,
|
|
30
|
+
onChange = () => {},
|
|
31
|
+
children = null,
|
|
32
|
+
}) => html`
|
|
33
|
+
<label
|
|
34
|
+
class=${`relative inline-flex shrink-0 items-center justify-end max-w-[12rem] min-w-[5.5rem] ${disabled
|
|
35
|
+
? "opacity-50 cursor-not-allowed"
|
|
36
|
+
: "cursor-pointer"}`}
|
|
37
|
+
>
|
|
38
|
+
<select
|
|
39
|
+
aria-label=${ariaLabel}
|
|
40
|
+
title=${title || ariaLabel}
|
|
41
|
+
value=${value}
|
|
42
|
+
disabled=${disabled}
|
|
43
|
+
onInput=${(event) => onChange(String(event.currentTarget?.value ?? ""))}
|
|
44
|
+
class="appearance-none bg-transparent border-0 py-0 pl-0 pr-5 w-full text-right text-xs text-fg-muted hover:text-body cursor-pointer focus:outline-none focus-visible:ring-1 focus-visible:ring-border rounded disabled:cursor-not-allowed truncate"
|
|
45
|
+
>
|
|
46
|
+
${children}
|
|
47
|
+
</select>
|
|
48
|
+
<span class="pointer-events-none absolute right-0 top-1/2 -translate-y-1/2">
|
|
49
|
+
<${RowAccessoryChevron} />
|
|
50
|
+
</span>
|
|
51
|
+
</label>
|
|
52
|
+
`;
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -857,6 +857,13 @@ export const fetchModelStatus = async () => {
|
|
|
857
857
|
return res.json();
|
|
858
858
|
};
|
|
859
859
|
|
|
860
|
+
export const fetchThinkingOptions = async (modelKey) => {
|
|
861
|
+
const normalized = String(modelKey || "").trim();
|
|
862
|
+
const qs = new URLSearchParams({ modelKey: normalized });
|
|
863
|
+
const res = await authFetch(`/api/models/thinking-options?${qs.toString()}`);
|
|
864
|
+
return res.json();
|
|
865
|
+
};
|
|
866
|
+
|
|
860
867
|
export const setPrimaryModel = async (modelKey) => {
|
|
861
868
|
const res = await authFetch("/api/models/set", {
|
|
862
869
|
method: "POST",
|
|
@@ -1245,8 +1252,11 @@ export async function fetchWebhookRequest(name, id) {
|
|
|
1245
1252
|
return parseJsonOrThrow(res, "Could not load webhook request");
|
|
1246
1253
|
}
|
|
1247
1254
|
|
|
1248
|
-
export const fetchBrowseTree = async (
|
|
1255
|
+
export const fetchBrowseTree = async (options = {}) => {
|
|
1256
|
+
const { depth = 3, path = "" } =
|
|
1257
|
+
typeof options === "number" ? { depth: options, path: "" } : options;
|
|
1249
1258
|
const params = new URLSearchParams({ depth: String(depth) });
|
|
1259
|
+
if (path) params.set("path", String(path));
|
|
1250
1260
|
const res = await authFetch(`/api/browse/tree?${params.toString()}`);
|
|
1251
1261
|
return parseJsonOrThrow(res, "Could not load file tree");
|
|
1252
1262
|
};
|
|
@@ -4,6 +4,7 @@ import { getFeaturedModels } from "./model-config.js";
|
|
|
4
4
|
|
|
5
5
|
export const kModelCatalogCacheKey = "/api/models";
|
|
6
6
|
export const kModelCatalogPollIntervalMs = 3000;
|
|
7
|
+
export const kDefaultOnboardingModelKey = "anthropic/claude-opus-4-8";
|
|
7
8
|
|
|
8
9
|
export const getModelCatalogModels = (payload) =>
|
|
9
10
|
Array.isArray(payload?.models) ? payload.models : [];
|
|
@@ -26,6 +27,11 @@ export const getInitialOnboardingModelKey = ({
|
|
|
26
27
|
} = {}) => {
|
|
27
28
|
const normalizedCurrent = String(currentModelKey || "").trim();
|
|
28
29
|
if (normalizedCurrent) return normalizedCurrent;
|
|
30
|
+
const catalogHasKey = (key) =>
|
|
31
|
+
catalog.some((model) => String(model?.key || "") === key);
|
|
32
|
+
if (catalogHasKey(kDefaultOnboardingModelKey)) {
|
|
33
|
+
return kDefaultOnboardingModelKey;
|
|
34
|
+
}
|
|
29
35
|
const featuredModels = getFeaturedModels(catalog);
|
|
30
36
|
return String(featuredModels[0]?.key || catalog[0]?.key || "");
|
|
31
37
|
};
|
|
@@ -9,6 +9,10 @@ export const getAuthProviderFromModelProvider = (provider) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const kFeaturedModelDefs = [
|
|
12
|
+
{
|
|
13
|
+
label: "Opus 4.8",
|
|
14
|
+
preferredKeys: ["anthropic/claude-opus-4-8"],
|
|
15
|
+
},
|
|
12
16
|
{
|
|
13
17
|
label: "Opus 4.7",
|
|
14
18
|
preferredKeys: ["anthropic/claude-opus-4-7"],
|
|
@@ -58,13 +62,14 @@ export const kProviderAuthFields = {
|
|
|
58
62
|
linkText: "Get key",
|
|
59
63
|
placeholder: "sk-ant-...",
|
|
60
64
|
},
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
// Temporarily hidden — setup-token flow is not supported in onboarding yet.
|
|
66
|
+
// {
|
|
67
|
+
// key: "ANTHROPIC_TOKEN",
|
|
68
|
+
// label: "Anthropic Setup Token",
|
|
69
|
+
// hint: "From claude setup-token (uses your Claude subscription)",
|
|
70
|
+
// linkText: "Get token",
|
|
71
|
+
// placeholder: "Token...",
|
|
72
|
+
// },
|
|
68
73
|
],
|
|
69
74
|
openai: [
|
|
70
75
|
{
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const kThinkingLevelLabelOverrides = {
|
|
2
|
+
off: "Off",
|
|
3
|
+
on: "On",
|
|
4
|
+
minimal: "Minimal",
|
|
5
|
+
low: "Low",
|
|
6
|
+
medium: "Medium",
|
|
7
|
+
high: "High",
|
|
8
|
+
adaptive: "Adaptive",
|
|
9
|
+
xhigh: "Extra high",
|
|
10
|
+
max: "Maximum",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const formatThinkingLevelLabel = (levelId = "") => {
|
|
14
|
+
const normalized = String(levelId || "").trim().toLowerCase();
|
|
15
|
+
if (!normalized) return "";
|
|
16
|
+
if (kThinkingLevelLabelOverrides[normalized]) {
|
|
17
|
+
return kThinkingLevelLabelOverrides[normalized];
|
|
18
|
+
}
|
|
19
|
+
return normalized
|
|
20
|
+
.split(/[-_]/g)
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
23
|
+
.join(" ");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const formatInheritedThinkingLabel = (levelId = "") => {
|
|
27
|
+
const label = formatThinkingLevelLabel(levelId);
|
|
28
|
+
return label ? `Inherited: ${label}` : "Inherited";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const shouldShowThinkingLevelSelect = (levels = []) => {
|
|
32
|
+
const normalized = (Array.isArray(levels) ? levels : [])
|
|
33
|
+
.map((entry) => String(entry?.id || entry || "").trim().toLowerCase())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
if (normalized.length === 0) return false;
|
|
36
|
+
return !(normalized.length === 1 && normalized[0] === "off");
|
|
37
|
+
};
|