@chrysb/alphaclaw 0.4.6-beta.4 → 0.4.6-beta.6
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/app.js +158 -1073
- package/lib/public/js/components/envars.js +146 -29
- package/lib/public/js/components/features.js +1 -1
- package/lib/public/js/components/general/index.js +155 -0
- package/lib/public/js/components/icons.js +52 -0
- package/lib/public/js/components/info-tooltip.js +4 -7
- package/lib/public/js/components/models-tab/index.js +286 -0
- package/lib/public/js/components/models-tab/provider-auth-card.js +369 -0
- package/lib/public/js/components/models-tab/use-models.js +262 -0
- package/lib/public/js/components/models.js +1 -1
- package/lib/public/js/components/providers.js +1 -1
- package/lib/public/js/components/routes/browse-route.js +35 -0
- package/lib/public/js/components/routes/doctor-route.js +21 -0
- package/lib/public/js/components/routes/envars-route.js +11 -0
- package/lib/public/js/components/routes/general-route.js +45 -0
- package/lib/public/js/components/routes/index.js +11 -0
- package/lib/public/js/components/routes/models-route.js +11 -0
- package/lib/public/js/components/routes/providers-route.js +11 -0
- package/lib/public/js/components/routes/route-redirect.js +10 -0
- package/lib/public/js/components/routes/telegram-route.js +11 -0
- package/lib/public/js/components/routes/usage-route.js +15 -0
- package/lib/public/js/components/routes/watchdog-route.js +32 -0
- package/lib/public/js/components/routes/webhooks-route.js +43 -0
- package/lib/public/js/components/sidebar.js +2 -3
- package/lib/public/js/components/tooltip.js +106 -0
- package/lib/public/js/components/usage-tab/constants.js +1 -1
- package/lib/public/js/components/usage-tab/overview-section.js +124 -50
- package/lib/public/js/components/usage-tab/use-usage-tab.js +42 -11
- package/lib/public/js/components/welcome.js +1 -1
- package/lib/public/js/hooks/use-app-shell-controller.js +230 -0
- package/lib/public/js/hooks/use-app-shell-ui.js +112 -0
- package/lib/public/js/hooks/use-browse-navigation.js +193 -0
- package/lib/public/js/hooks/use-hash-location.js +32 -0
- package/lib/public/js/lib/api.js +35 -0
- package/lib/public/js/lib/app-navigation.js +39 -0
- package/lib/public/js/lib/browse-restart-policy.js +28 -0
- package/lib/public/js/lib/browse-route.js +57 -0
- package/lib/public/js/lib/format.js +12 -0
- package/lib/public/js/lib/model-config.js +1 -0
- package/lib/server/auth-profiles.js +291 -53
- package/lib/server/constants.js +24 -8
- package/lib/server/doctor/service.js +0 -3
- package/lib/server/gateway.js +50 -31
- package/lib/server/onboarding/index.js +2 -0
- package/lib/server/onboarding/validation.js +2 -2
- package/lib/server/routes/models.js +214 -2
- package/lib/server/routes/onboarding.js +2 -0
- package/lib/server/routes/system.js +42 -1
- package/lib/server/watchdog.js +14 -1
- package/lib/server.js +6 -0
- package/lib/setup/env.template +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from "https://esm.sh/preact/hooks";
|
|
2
|
+
import { readUiSettings, writeUiSettings } from "../lib/ui-settings.js";
|
|
3
|
+
import { kDefaultUiTab, getSelectedNavId, kNavSections } from "../lib/app-navigation.js";
|
|
4
|
+
import { buildBrowseRoute, normalizeBrowsePath, parseBrowseRoute } from "../lib/browse-route.js";
|
|
5
|
+
|
|
6
|
+
const kBrowseLastPathUiSettingKey = "browseLastPath";
|
|
7
|
+
const kLastMenuRouteUiSettingKey = "lastMenuRoute";
|
|
8
|
+
|
|
9
|
+
export const useBrowseNavigation = ({
|
|
10
|
+
location = "",
|
|
11
|
+
setLocation = () => {},
|
|
12
|
+
onCloseMobileSidebar = () => {},
|
|
13
|
+
} = {}) => {
|
|
14
|
+
const [sidebarTab, setSidebarTab] = useState(() =>
|
|
15
|
+
location.startsWith("/browse") ? "browse" : "menu",
|
|
16
|
+
);
|
|
17
|
+
const [lastBrowsePath, setLastBrowsePath] = useState(() => {
|
|
18
|
+
const settings = readUiSettings();
|
|
19
|
+
return typeof settings[kBrowseLastPathUiSettingKey] === "string"
|
|
20
|
+
? settings[kBrowseLastPathUiSettingKey]
|
|
21
|
+
: "";
|
|
22
|
+
});
|
|
23
|
+
const [lastMenuRoute, setLastMenuRoute] = useState(() => {
|
|
24
|
+
const settings = readUiSettings();
|
|
25
|
+
const storedRoute = settings[kLastMenuRouteUiSettingKey];
|
|
26
|
+
if (
|
|
27
|
+
typeof storedRoute === "string" &&
|
|
28
|
+
storedRoute.startsWith("/") &&
|
|
29
|
+
!storedRoute.startsWith("/browse")
|
|
30
|
+
) {
|
|
31
|
+
return storedRoute;
|
|
32
|
+
}
|
|
33
|
+
return `/${kDefaultUiTab}`;
|
|
34
|
+
});
|
|
35
|
+
const [browsePreviewPath, setBrowsePreviewPath] = useState("");
|
|
36
|
+
const routeHistoryRef = useRef([]);
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
activeBrowsePath,
|
|
40
|
+
browseLineEndTarget,
|
|
41
|
+
browseLineTarget,
|
|
42
|
+
browseViewerMode,
|
|
43
|
+
isBrowseRoute,
|
|
44
|
+
selectedBrowsePath,
|
|
45
|
+
} = parseBrowseRoute({
|
|
46
|
+
location,
|
|
47
|
+
browsePreviewPath,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const selectedNavId = getSelectedNavId({
|
|
51
|
+
isBrowseRoute,
|
|
52
|
+
location,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
setSidebarTab((currentTab) => {
|
|
57
|
+
if (location.startsWith("/browse")) return "browse";
|
|
58
|
+
if (currentTab === "browse") return "menu";
|
|
59
|
+
return currentTab;
|
|
60
|
+
});
|
|
61
|
+
}, [location]);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (location.startsWith("/browse")) return;
|
|
65
|
+
setBrowsePreviewPath("");
|
|
66
|
+
}, [location]);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const historyStack = routeHistoryRef.current;
|
|
70
|
+
const lastEntry = historyStack[historyStack.length - 1];
|
|
71
|
+
if (lastEntry === location) return;
|
|
72
|
+
historyStack.push(location);
|
|
73
|
+
if (historyStack.length > 100) {
|
|
74
|
+
historyStack.shift();
|
|
75
|
+
}
|
|
76
|
+
}, [location]);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (location.startsWith("/browse")) return;
|
|
80
|
+
if (location === "/telegram") return;
|
|
81
|
+
setLastMenuRoute((currentRoute) =>
|
|
82
|
+
currentRoute === location ? currentRoute : location,
|
|
83
|
+
);
|
|
84
|
+
}, [location]);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (!isBrowseRoute) return;
|
|
88
|
+
if (!selectedBrowsePath) return;
|
|
89
|
+
setLastBrowsePath((currentPath) =>
|
|
90
|
+
currentPath === selectedBrowsePath ? currentPath : selectedBrowsePath,
|
|
91
|
+
);
|
|
92
|
+
}, [isBrowseRoute, selectedBrowsePath]);
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const handleBrowseGitSynced = () => {
|
|
96
|
+
if (!isBrowseRoute || browseViewerMode !== "diff") return;
|
|
97
|
+
const activePath = String(selectedBrowsePath || "").trim();
|
|
98
|
+
if (!activePath) return;
|
|
99
|
+
setLocation(buildBrowseRoute(activePath, { view: "edit" }));
|
|
100
|
+
};
|
|
101
|
+
window.addEventListener("alphaclaw:browse-git-synced", handleBrowseGitSynced);
|
|
102
|
+
return () => {
|
|
103
|
+
window.removeEventListener("alphaclaw:browse-git-synced", handleBrowseGitSynced);
|
|
104
|
+
};
|
|
105
|
+
}, [browseViewerMode, isBrowseRoute, selectedBrowsePath, setLocation]);
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
const settings = readUiSettings();
|
|
109
|
+
settings[kBrowseLastPathUiSettingKey] = lastBrowsePath;
|
|
110
|
+
settings[kLastMenuRouteUiSettingKey] = lastMenuRoute;
|
|
111
|
+
writeUiSettings(settings);
|
|
112
|
+
}, [lastBrowsePath, lastMenuRoute]);
|
|
113
|
+
|
|
114
|
+
const navigateToSubScreen = useCallback((screen) => {
|
|
115
|
+
setLocation(`/${screen}`);
|
|
116
|
+
onCloseMobileSidebar();
|
|
117
|
+
}, [onCloseMobileSidebar, setLocation]);
|
|
118
|
+
|
|
119
|
+
const handleBrowsePreviewFile = useCallback((nextPreviewPath) => {
|
|
120
|
+
const normalizedPreviewPath = normalizeBrowsePath(nextPreviewPath);
|
|
121
|
+
setBrowsePreviewPath(normalizedPreviewPath);
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
const navigateToBrowseFile = useCallback((relativePath, options = {}) => {
|
|
125
|
+
const normalizedTargetPath = normalizeBrowsePath(relativePath);
|
|
126
|
+
const selectingDirectory =
|
|
127
|
+
!!options.directory || String(relativePath || "").trim().endsWith("/");
|
|
128
|
+
const shouldPreservePreview = selectingDirectory && !!options.preservePreview;
|
|
129
|
+
const activePath = normalizeBrowsePath(
|
|
130
|
+
browsePreviewPath || selectedBrowsePath || "",
|
|
131
|
+
);
|
|
132
|
+
const nextPreviewPath =
|
|
133
|
+
shouldPreservePreview && activePath && activePath !== normalizedTargetPath
|
|
134
|
+
? activePath
|
|
135
|
+
: "";
|
|
136
|
+
setBrowsePreviewPath(nextPreviewPath);
|
|
137
|
+
const routeOptions = selectingDirectory
|
|
138
|
+
? { ...options, view: "edit" }
|
|
139
|
+
: options;
|
|
140
|
+
setLocation(buildBrowseRoute(normalizedTargetPath, routeOptions));
|
|
141
|
+
onCloseMobileSidebar();
|
|
142
|
+
}, [browsePreviewPath, onCloseMobileSidebar, selectedBrowsePath, setLocation]);
|
|
143
|
+
|
|
144
|
+
const handleSelectSidebarTab = useCallback((nextTab) => {
|
|
145
|
+
setSidebarTab(nextTab);
|
|
146
|
+
if (nextTab === "menu" && location.startsWith("/browse")) {
|
|
147
|
+
setBrowsePreviewPath("");
|
|
148
|
+
setLocation(lastMenuRoute || `/${kDefaultUiTab}`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (nextTab === "browse" && !location.startsWith("/browse")) {
|
|
152
|
+
setLocation(buildBrowseRoute(lastBrowsePath));
|
|
153
|
+
}
|
|
154
|
+
}, [lastBrowsePath, lastMenuRoute, location, setLocation]);
|
|
155
|
+
|
|
156
|
+
const handleSelectNavItem = useCallback((itemId) => {
|
|
157
|
+
setLocation(`/${itemId}`);
|
|
158
|
+
onCloseMobileSidebar();
|
|
159
|
+
}, [onCloseMobileSidebar, setLocation]);
|
|
160
|
+
|
|
161
|
+
const exitSubScreen = useCallback(() => {
|
|
162
|
+
setLocation(`/${kDefaultUiTab}`);
|
|
163
|
+
onCloseMobileSidebar();
|
|
164
|
+
}, [onCloseMobileSidebar, setLocation]);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
state: {
|
|
168
|
+
activeBrowsePath,
|
|
169
|
+
browseLineEndTarget,
|
|
170
|
+
browseLineTarget,
|
|
171
|
+
browsePreviewPath,
|
|
172
|
+
browseViewerMode,
|
|
173
|
+
isBrowseRoute,
|
|
174
|
+
routeHistoryRef,
|
|
175
|
+
selectedBrowsePath,
|
|
176
|
+
selectedNavId,
|
|
177
|
+
sidebarTab,
|
|
178
|
+
},
|
|
179
|
+
actions: {
|
|
180
|
+
buildBrowseRoute,
|
|
181
|
+
clearBrowsePreview: () => setBrowsePreviewPath(""),
|
|
182
|
+
exitSubScreen,
|
|
183
|
+
handleBrowsePreviewFile,
|
|
184
|
+
handleSelectNavItem,
|
|
185
|
+
handleSelectSidebarTab,
|
|
186
|
+
navigateToBrowseFile,
|
|
187
|
+
navigateToSubScreen,
|
|
188
|
+
},
|
|
189
|
+
constants: {
|
|
190
|
+
kNavSections,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "https://esm.sh/preact/hooks";
|
|
2
|
+
import { kDefaultUiTab } from "../lib/app-navigation.js";
|
|
3
|
+
|
|
4
|
+
const getHashPath = () => {
|
|
5
|
+
const hash = window.location.hash.replace(/^#/, "");
|
|
6
|
+
if (!hash) return `/${kDefaultUiTab}`;
|
|
7
|
+
return hash.startsWith("/") ? hash : `/${hash}`;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const useHashLocation = () => {
|
|
11
|
+
const [location, setLocationState] = useState(getHashPath);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const onHashChange = () => setLocationState(getHashPath());
|
|
15
|
+
window.addEventListener("hashchange", onHashChange);
|
|
16
|
+
return () => window.removeEventListener("hashchange", onHashChange);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
const setLocation = useCallback((to) => {
|
|
20
|
+
const normalized = to.startsWith("/") ? to : `/${to}`;
|
|
21
|
+
const nextHash = `#${normalized}`;
|
|
22
|
+
if (window.location.hash !== nextHash) {
|
|
23
|
+
window.location.hash = normalized;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
setLocationState(normalized);
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
return [location, setLocation];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const getHashRouterPath = getHashPath;
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -496,6 +496,41 @@ export const setPrimaryModel = async (modelKey) => {
|
|
|
496
496
|
return res.json();
|
|
497
497
|
};
|
|
498
498
|
|
|
499
|
+
export const fetchModelsConfig = async () => {
|
|
500
|
+
const res = await authFetch('/api/models/config');
|
|
501
|
+
return res.json();
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
export const saveModelsConfig = async ({ primary, configuredModels, profiles, authOrder }) => {
|
|
505
|
+
const res = await authFetch('/api/models/config', {
|
|
506
|
+
method: 'PUT',
|
|
507
|
+
headers: { 'Content-Type': 'application/json' },
|
|
508
|
+
body: JSON.stringify({ primary, configuredModels, profiles, authOrder }),
|
|
509
|
+
});
|
|
510
|
+
return res.json();
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
export const fetchAuthProfiles = async () => {
|
|
514
|
+
const res = await authFetch('/api/models/auth');
|
|
515
|
+
return res.json();
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
export const upsertAuthProfile = async (profileId, credential) => {
|
|
519
|
+
const res = await authFetch(`/api/models/auth/${encodeURIComponent(profileId)}`, {
|
|
520
|
+
method: 'PUT',
|
|
521
|
+
headers: { 'Content-Type': 'application/json' },
|
|
522
|
+
body: JSON.stringify(credential),
|
|
523
|
+
});
|
|
524
|
+
return res.json();
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
export const deleteAuthProfile = async (profileId) => {
|
|
528
|
+
const res = await authFetch(`/api/models/auth/${encodeURIComponent(profileId)}`, {
|
|
529
|
+
method: 'DELETE',
|
|
530
|
+
});
|
|
531
|
+
return res.json();
|
|
532
|
+
};
|
|
533
|
+
|
|
499
534
|
export const fetchCodexStatus = async () => {
|
|
500
535
|
const res = await authFetch('/api/codex/status');
|
|
501
536
|
return res.json();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const kDefaultUiTab = "general";
|
|
2
|
+
|
|
3
|
+
export const kNavSections = [
|
|
4
|
+
{
|
|
5
|
+
label: "Setup",
|
|
6
|
+
items: [
|
|
7
|
+
{ id: "general", label: "General" },
|
|
8
|
+
],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
label: "Monitoring",
|
|
12
|
+
items: [
|
|
13
|
+
{ id: "watchdog", label: "Watchdog" },
|
|
14
|
+
{ id: "usage", label: "Usage" },
|
|
15
|
+
{ id: "doctor", label: "Doctor" },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: "Config",
|
|
20
|
+
items: [
|
|
21
|
+
{ id: "models", label: "Models" },
|
|
22
|
+
{ id: "envars", label: "Envars" },
|
|
23
|
+
{ id: "webhooks", label: "Webhooks" },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export const getSelectedNavId = ({ isBrowseRoute = false, location = "" } = {}) => {
|
|
29
|
+
if (isBrowseRoute) return "browse";
|
|
30
|
+
if (location === "/telegram") return "";
|
|
31
|
+
if (location.startsWith("/models")) return "models";
|
|
32
|
+
if (location.startsWith("/providers")) return "models";
|
|
33
|
+
if (location.startsWith("/watchdog")) return "watchdog";
|
|
34
|
+
if (location.startsWith("/usage")) return "usage";
|
|
35
|
+
if (location.startsWith("/doctor")) return "doctor";
|
|
36
|
+
if (location.startsWith("/envars")) return "envars";
|
|
37
|
+
if (location.startsWith("/webhooks")) return "webhooks";
|
|
38
|
+
return kDefaultUiTab;
|
|
39
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const kBrowseRestartRequiredRules = [
|
|
2
|
+
{ type: "file", path: "openclaw.json" },
|
|
3
|
+
{ type: "directory", path: "hooks/transforms" },
|
|
4
|
+
];
|
|
5
|
+
|
|
6
|
+
const normalizeRestartRulePath = (value) =>
|
|
7
|
+
String(value || "")
|
|
8
|
+
.trim()
|
|
9
|
+
.replace(/^\/+|\/+$/g, "");
|
|
10
|
+
|
|
11
|
+
const matchesBrowseRestartRequiredRule = (path, rule) => {
|
|
12
|
+
const normalizedPath = normalizeRestartRulePath(path);
|
|
13
|
+
if (!normalizedPath) return false;
|
|
14
|
+
if (!rule || typeof rule !== "object") return false;
|
|
15
|
+
const type = String(rule.type || "").toLowerCase();
|
|
16
|
+
const targetPath = normalizeRestartRulePath(rule.path);
|
|
17
|
+
if (!targetPath) return false;
|
|
18
|
+
if (type === "directory") {
|
|
19
|
+
return normalizedPath === targetPath || normalizedPath.startsWith(`${targetPath}/`);
|
|
20
|
+
}
|
|
21
|
+
if (type === "file") {
|
|
22
|
+
return normalizedPath === targetPath;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const shouldRequireRestartForBrowsePath = (path) =>
|
|
28
|
+
kBrowseRestartRequiredRules.some((rule) => matchesBrowseRestartRequiredRule(path, rule));
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const normalizeBrowsePath = (value) => String(value || "").replace(/^\/+|\/+$/g, "");
|
|
2
|
+
|
|
3
|
+
export const buildBrowseRoute = (relativePath, options = {}) => {
|
|
4
|
+
const view = String(options?.view || "edit");
|
|
5
|
+
const encodedPath = String(relativePath || "")
|
|
6
|
+
.split("/")
|
|
7
|
+
.filter(Boolean)
|
|
8
|
+
.map((segment) => encodeURIComponent(segment))
|
|
9
|
+
.join("/");
|
|
10
|
+
const baseRoute = encodedPath ? `/browse/${encodedPath}` : "/browse";
|
|
11
|
+
const params = new URLSearchParams();
|
|
12
|
+
if (view === "diff" && encodedPath) params.set("view", "diff");
|
|
13
|
+
if (options.line) params.set("line", String(options.line));
|
|
14
|
+
if (options.lineEnd) params.set("lineEnd", String(options.lineEnd));
|
|
15
|
+
const query = params.toString();
|
|
16
|
+
return query ? `${baseRoute}?${query}` : baseRoute;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const parseBrowseRoute = ({ location = "", browsePreviewPath = "" } = {}) => {
|
|
20
|
+
const isBrowseRoute = location.startsWith("/browse");
|
|
21
|
+
const browseRoutePath = isBrowseRoute ? String(location || "").split("?")[0] : "";
|
|
22
|
+
const browseRouteQuery =
|
|
23
|
+
isBrowseRoute && String(location || "").includes("?")
|
|
24
|
+
? String(location || "").split("?").slice(1).join("?")
|
|
25
|
+
: "";
|
|
26
|
+
const selectedBrowsePath = isBrowseRoute
|
|
27
|
+
? browseRoutePath
|
|
28
|
+
.replace(/^\/browse\/?/, "")
|
|
29
|
+
.split("/")
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.map((segment) => {
|
|
32
|
+
try {
|
|
33
|
+
return decodeURIComponent(segment);
|
|
34
|
+
} catch {
|
|
35
|
+
return segment;
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
.join("/")
|
|
39
|
+
: "";
|
|
40
|
+
const activeBrowsePath = browsePreviewPath || selectedBrowsePath;
|
|
41
|
+
const browseQueryParams = isBrowseRoute ? new URLSearchParams(browseRouteQuery) : null;
|
|
42
|
+
const browseViewerMode =
|
|
43
|
+
!browsePreviewPath && browseQueryParams?.get("view") === "diff"
|
|
44
|
+
? "diff"
|
|
45
|
+
: "edit";
|
|
46
|
+
const browseLineTarget = Number.parseInt(browseQueryParams?.get("line") || "", 10) || 0;
|
|
47
|
+
const browseLineEndTarget = Number.parseInt(browseQueryParams?.get("lineEnd") || "", 10) || 0;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
activeBrowsePath,
|
|
51
|
+
browseLineEndTarget,
|
|
52
|
+
browseLineTarget,
|
|
53
|
+
browseViewerMode,
|
|
54
|
+
isBrowseRoute,
|
|
55
|
+
selectedBrowsePath,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
const kIntegerFormatter = new Intl.NumberFormat("en-US");
|
|
2
|
+
const kCompactNumberFormatter = new Intl.NumberFormat("en-US", {
|
|
3
|
+
notation: "compact",
|
|
4
|
+
minimumFractionDigits: 1,
|
|
5
|
+
maximumFractionDigits: 1,
|
|
6
|
+
});
|
|
2
7
|
const kUsdFormatter = new Intl.NumberFormat("en-US", {
|
|
3
8
|
style: "currency",
|
|
4
9
|
currency: "USD",
|
|
@@ -25,6 +30,13 @@ const isSameDay = (left, right) =>
|
|
|
25
30
|
export const formatInteger = (value) =>
|
|
26
31
|
kIntegerFormatter.format(Number(value || 0));
|
|
27
32
|
|
|
33
|
+
export const formatCompactNumber = (value) => {
|
|
34
|
+
const numberValue = Number(value || 0);
|
|
35
|
+
if (!Number.isFinite(numberValue)) return "0";
|
|
36
|
+
if (Math.abs(numberValue) < 1000) return formatInteger(numberValue);
|
|
37
|
+
return kCompactNumberFormatter.format(numberValue);
|
|
38
|
+
};
|
|
39
|
+
|
|
28
40
|
export const formatUsd = (value) => kUsdFormatter.format(Number(value || 0));
|
|
29
41
|
|
|
30
42
|
export const formatLocaleDateTime = (
|
|
@@ -158,6 +158,7 @@ export const kFeatureDefs = [
|
|
|
158
158
|
];
|
|
159
159
|
|
|
160
160
|
export const getVisibleAiFieldKeys = (provider) => {
|
|
161
|
+
if (provider === "openai-codex") return new Set();
|
|
161
162
|
const authProvider = getAuthProviderFromModelProvider(provider);
|
|
162
163
|
const fields = kProviderAuthFields[authProvider] || [];
|
|
163
164
|
return new Set(fields.map((field) => field.key));
|