mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-03 18:23:51 +08:00
feat(overview): 近4小时/近24小时移入自定义弹层,近4小时支持选择图表粒度
- 主时间栏移除近4小时/近24小时,改为自定义弹层顶部的快捷范围 - 近4小时点击后弹出粒度菜单:1分钟/15分钟/30分钟/1小时 - overview API 新增 bucket_ms 参数,支持客户端指定分桶粒度 - 指定粒度时桶数上限放宽至 1440,未指定时维持原自动逻辑
This commit is contained in:
@@ -5,7 +5,7 @@ import { DailyTokenUsageChart } from "./charts/DailyTokenUsageChart";
|
||||
import { HomeMetrics } from "./metrics/HomeMetrics";
|
||||
import { PageContent } from "../../shell/layout/PageContent";
|
||||
import type { VirtualPageSection } from "../../shell/layout/VirtualPage";
|
||||
import { OverviewTimeRangeFilter, type OverviewRangePreset } from "./overview/OverviewTimeRangeFilter";
|
||||
import { OverviewTimeRangeFilter, type OverviewRangePreset, type QuickPreset } from "./overview/OverviewTimeRangeFilter";
|
||||
import { PageActions } from "../../shell/PageActions";
|
||||
import { appStore, useAppStore } from "../../shared/store/appStore";
|
||||
import { formatTimeInput, parseTimeInput } from "../../shared/utils/parseTimeInput";
|
||||
@@ -25,13 +25,17 @@ function presetRange(preset: Exclude<OverviewRangePreset, "custom">, now = new D
|
||||
start.setMonth(start.getMonth() - 1);
|
||||
return { startMs: start.getTime(), endMs };
|
||||
}
|
||||
const duration = preset === "ten-minutes" ? 10 * 60_000 : preset === "hour" ? 60 * 60_000 : 7 * 24 * 60 * 60_000;
|
||||
const duration = preset === "ten-minutes" ? 10 * 60_000
|
||||
: preset === "hour" ? 60 * 60_000
|
||||
: 7 * 24 * 60 * 60_000;
|
||||
return { startMs: now.getTime() - duration, endMs };
|
||||
}
|
||||
|
||||
export function HomePage() {
|
||||
const { overview, busy, models, plugins } = useAppStore();
|
||||
const [preset, setPreset] = useState<OverviewRangePreset>("month");
|
||||
const [quick, setQuick] = useState<QuickPreset | null>(null);
|
||||
const [fourHourBucket, setFourHourBucket] = useState<number | undefined>(undefined);
|
||||
const [customRange, setCustomRange] = useState<TimeRange | null>(null);
|
||||
const [customOpen, setCustomOpen] = useState(false);
|
||||
const [customStart, setCustomStart] = useState("");
|
||||
@@ -50,13 +54,14 @@ export function HomePage() {
|
||||
void api.overview({
|
||||
...selectedRange,
|
||||
modelHashes: appliedModels,
|
||||
bucketMs: fourHourBucket,
|
||||
}).then((next) => {
|
||||
if (active) setRangeOverview(next);
|
||||
}).finally(() => {
|
||||
if (active) setRangeBusy(false);
|
||||
});
|
||||
return () => { active = false; };
|
||||
}, [preset, customRange, overview, refreshVersion, appliedModels]);
|
||||
}, [preset, customRange, overview, refreshVersion, appliedModels, fourHourBucket]);
|
||||
|
||||
const filteredOverview = rangeOverview ?? overview;
|
||||
const dailyTokenUsage = filteredOverview.token_usage_series.map((bucket) => ({
|
||||
@@ -93,9 +98,26 @@ export function HomePage() {
|
||||
if (startMs === null || endMs === null || startMs >= endMs) return;
|
||||
setCustomRange({ startMs, endMs });
|
||||
setAppliedModels(selectedModels);
|
||||
setQuick(null);
|
||||
setFourHourBucket(undefined);
|
||||
setPreset("custom");
|
||||
setCustomOpen(false);
|
||||
};
|
||||
const selectQuick = (durationMs: number, bucketMs?: number) => {
|
||||
const endMs = Date.now();
|
||||
setCustomRange({ startMs: endMs - durationMs, endMs });
|
||||
setAppliedModels(selectedModels);
|
||||
setQuick(durationMs === 4 * 60 * 60_000 ? "four-hours" : "twenty-four-hours");
|
||||
setFourHourBucket(durationMs === 4 * 60 * 60_000 ? bucketMs : undefined);
|
||||
setPreset("custom");
|
||||
setCustomOpen(false);
|
||||
};
|
||||
const selectPreset = (value: Exclude<OverviewRangePreset, "custom">) => {
|
||||
setPreset(value);
|
||||
setQuick(null);
|
||||
setFourHourBucket(undefined);
|
||||
setCustomOpen(false);
|
||||
};
|
||||
const refresh = async () => {
|
||||
await appStore.refresh();
|
||||
setRefreshVersion((version) => version + 1);
|
||||
@@ -138,13 +160,16 @@ export function HomePage() {
|
||||
return <>
|
||||
<PageActions><OverviewTimeRangeFilter
|
||||
value={preset}
|
||||
quick={quick}
|
||||
fourHourBucket={fourHourBucket}
|
||||
customOpen={customOpen}
|
||||
customStart={customStart}
|
||||
customEnd={customEnd}
|
||||
modelOptions={modelOptions}
|
||||
selectedModels={selectedModels}
|
||||
busy={busy || rangeBusy}
|
||||
onSelect={(value) => { setPreset(value); setCustomOpen(false); }}
|
||||
onSelect={selectPreset}
|
||||
onQuickSelect={selectQuick}
|
||||
onCustomOpenChange={openCustom}
|
||||
onCustomStartChange={setCustomStart}
|
||||
onCustomEndChange={setCustomEnd}
|
||||
|
||||
@@ -76,3 +76,50 @@
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.quickPresets {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
|
||||
button {
|
||||
height: 26px;
|
||||
padding: 0 10px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
background: var(--vscode-button-secondaryBackground, var(--vscode-sideBar-background));
|
||||
border: 1px solid var(--vscode-sideBar-border);
|
||||
border-radius: 4px;
|
||||
font-size: type.$font-size-xs;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover { color: var(--vscode-foreground); background: var(--vscode-list-hoverBackground); }
|
||||
&[aria-pressed="true"] { color: var(--vscode-list-activeSelectionForeground); background: var(--vscode-list-activeSelectionBackground); border-color: transparent; }
|
||||
}
|
||||
}
|
||||
|
||||
.granularityMenu {
|
||||
position: fixed;
|
||||
z-index: 15000;
|
||||
min-width: 96px;
|
||||
padding: 4px;
|
||||
background: var(--vscode-dropdown-background);
|
||||
border: 1px solid var(--vscode-dropdown-border);
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--oa-dropdown-shadow);
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
min-height: 28px;
|
||||
display: block;
|
||||
padding: 4px 8px;
|
||||
color: var(--vscode-dropdown-foreground);
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
font-size: type.$font-size-xs;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover { background: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); }
|
||||
&[aria-pressed="true"] { background: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,12 @@ import { refreshIcon } from "../../../shared/ui/icons";
|
||||
import styles from "./OverviewTimeRangeFilter.module.scss";
|
||||
|
||||
export type OverviewRangePreset = "ten-minutes" | "hour" | "today" | "week" | "month" | "custom";
|
||||
export type QuickPreset = "four-hours" | "twenty-four-hours";
|
||||
|
||||
export function OverviewTimeRangeFilter({ value, customOpen, customStart, customEnd, modelOptions, selectedModels, busy, onSelect, onCustomOpenChange, onCustomStartChange, onCustomEndChange, onSelectedModelsChange, onCustomApply, onRefresh }: {
|
||||
export function OverviewTimeRangeFilter({ value, quick, fourHourBucket, customOpen, customStart, customEnd, modelOptions, selectedModels, busy, onSelect, onQuickSelect, onCustomOpenChange, onCustomStartChange, onCustomEndChange, onSelectedModelsChange, onCustomApply, onRefresh }: {
|
||||
value: OverviewRangePreset;
|
||||
quick: QuickPreset | null;
|
||||
fourHourBucket: number | undefined;
|
||||
customOpen: boolean;
|
||||
customStart: string;
|
||||
customEnd: string;
|
||||
@@ -20,6 +23,7 @@ export function OverviewTimeRangeFilter({ value, customOpen, customStart, custom
|
||||
selectedModels: string[];
|
||||
busy: boolean;
|
||||
onSelect: (value: Exclude<OverviewRangePreset, "custom">) => void;
|
||||
onQuickSelect: (durationMs: number, bucketMs?: number) => void;
|
||||
onCustomOpenChange: (open: boolean) => void;
|
||||
onCustomStartChange: (value: string) => void;
|
||||
onCustomEndChange: (value: string) => void;
|
||||
@@ -34,10 +38,25 @@ export function OverviewTimeRangeFilter({ value, customOpen, customStart, custom
|
||||
{ value: "week", label: t("近一周") },
|
||||
{ value: "month", label: t("近一个月") },
|
||||
];
|
||||
const quickPresets: Array<{ value: QuickPreset; label: string }> = [
|
||||
{ value: "four-hours", label: t("近4小时") },
|
||||
{ value: "twenty-four-hours", label: t("近24小时") },
|
||||
];
|
||||
const granularityOptions = [
|
||||
{ bucketMs: 60_000, label: t("1分钟") },
|
||||
{ bucketMs: 15 * 60_000, label: t("15分钟") },
|
||||
{ bucketMs: 30 * 60_000, label: t("30分钟") },
|
||||
{ bucketMs: 60 * 60_000, label: t("1小时") },
|
||||
];
|
||||
const customButton = useRef<HTMLButtonElement>(null);
|
||||
const popover = useRef<HTMLDivElement>(null);
|
||||
const fourHourButton = useRef<HTMLButtonElement>(null);
|
||||
const granularityMenu = useRef<HTMLDivElement>(null);
|
||||
const popoverId = useId();
|
||||
const granularityMenuId = useId();
|
||||
const [position, setPosition] = useState({ left: 0, top: 0, width: 300, maxHeight: 480 });
|
||||
const [menuPosition, setMenuPosition] = useState({ left: 0, top: 0 });
|
||||
const [granularityOpen, setGranularityOpen] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!customOpen || !customButton.current || !popover.current) return;
|
||||
@@ -53,11 +72,28 @@ export function OverviewTimeRangeFilter({ value, customOpen, customStart, custom
|
||||
}).then(({ x, y }) => setPosition((current) => ({ ...current, left: x, top: y }))));
|
||||
}, [customOpen]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!granularityOpen || !fourHourButton.current || !granularityMenu.current) return;
|
||||
return autoUpdate(fourHourButton.current, granularityMenu.current, () =>
|
||||
void computePosition(fourHourButton.current!, granularityMenu.current!, {
|
||||
placement: "right-start",
|
||||
middleware: [offset(4), flip({ padding: 10 }), shift({ padding: 10 })],
|
||||
}).then(({ x, y }) => setMenuPosition({ left: x, top: y })));
|
||||
}, [granularityOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!customOpen) setGranularityOpen(false);
|
||||
}, [customOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!customOpen) return;
|
||||
const closeOutside = (event: PointerEvent) => {
|
||||
const target = event.target as Node;
|
||||
if (!customButton.current?.contains(target) && !popover.current?.contains(target)) onCustomOpenChange(false);
|
||||
if (
|
||||
!customButton.current?.contains(target)
|
||||
&& !popover.current?.contains(target)
|
||||
&& !granularityMenu.current?.contains(target)
|
||||
) onCustomOpenChange(false);
|
||||
};
|
||||
document.addEventListener("pointerdown", closeOutside);
|
||||
return () => document.removeEventListener("pointerdown", closeOutside);
|
||||
@@ -66,6 +102,10 @@ export function OverviewTimeRangeFilter({ value, customOpen, customStart, custom
|
||||
const parsedStart = parseTimeInput(customStart);
|
||||
const parsedEnd = parseTimeInput(customEnd);
|
||||
const customValid = parsedStart !== null && parsedEnd !== null && parsedStart < parsedEnd;
|
||||
const closeGranularity = () => {
|
||||
setGranularityOpen(false);
|
||||
fourHourButton.current?.focus();
|
||||
};
|
||||
|
||||
return <div className={styles.root} aria-label={t("概览时间范围")}>
|
||||
<div className={styles.presets}>
|
||||
@@ -98,11 +138,37 @@ export function OverviewTimeRangeFilter({ value, customOpen, customStart, custom
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
if (granularityOpen) {
|
||||
closeGranularity();
|
||||
return;
|
||||
}
|
||||
onCustomOpenChange(false);
|
||||
customButton.current?.focus();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={styles.quickPresets} aria-label={t("快捷时间范围")}>
|
||||
{quickPresets.map((preset) => preset.value === "four-hours"
|
||||
? <button
|
||||
key={preset.value}
|
||||
ref={fourHourButton}
|
||||
type="button"
|
||||
aria-haspopup="menu"
|
||||
aria-controls={granularityOpen ? granularityMenuId : undefined}
|
||||
aria-expanded={granularityOpen}
|
||||
aria-pressed={quick === "four-hours"}
|
||||
onClick={() => setGranularityOpen((current) => !current)}
|
||||
>{preset.label}</button>
|
||||
: <button
|
||||
key={preset.value}
|
||||
type="button"
|
||||
aria-pressed={quick === "twenty-four-hours"}
|
||||
onClick={() => {
|
||||
setGranularityOpen(false);
|
||||
onQuickSelect(24 * 60 * 60_000);
|
||||
}}
|
||||
>{preset.label}</button>)}
|
||||
</div>
|
||||
<label><span>{t("开始时间")}</span><input type="text" placeholder={t("如:2026-08-23 09:00、1小时前")} value={customStart} onChange={(event) => onCustomStartChange(event.target.value)} /></label>
|
||||
<label><span>{t("结束时间")}</span><input type="text" placeholder={t("如:现在、2026-08-23 18:00")} value={customEnd} onChange={(event) => onCustomEndChange(event.target.value)} /></label>
|
||||
<div className={styles.filterRow}><MultiSelect label={t("模型")} value={selectedModels} options={modelOptions} onChange={onSelectedModelsChange} /></div>
|
||||
@@ -111,5 +177,31 @@ export function OverviewTimeRangeFilter({ value, customOpen, customStart, custom
|
||||
<button type="button" className={controls.primary} disabled={!customValid} onClick={onCustomApply}>{t("应用")}</button>
|
||||
</div>
|
||||
</div>, document.body)}
|
||||
{granularityOpen && customOpen && createPortal(<div
|
||||
id={granularityMenuId}
|
||||
ref={granularityMenu}
|
||||
className={styles.granularityMenu}
|
||||
role="menu"
|
||||
aria-label={t("显示粒度")}
|
||||
style={menuPosition}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Escape") {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
closeGranularity();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{granularityOptions.map((option) => <button
|
||||
key={option.bucketMs}
|
||||
type="button"
|
||||
role="menuitem"
|
||||
aria-pressed={quick === "four-hours" && fourHourBucket === option.bucketMs}
|
||||
onClick={() => {
|
||||
setGranularityOpen(false);
|
||||
onQuickSelect(4 * 60 * 60_000, option.bucketMs);
|
||||
}}
|
||||
>{option.label}</button>)}
|
||||
</div>, document.body)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -423,6 +423,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"100fad4a0b3ab781": {
|
||||
"source": "近24小时",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 43,
|
||||
"column": 42
|
||||
}
|
||||
]
|
||||
},
|
||||
"105a9082c346f958": {
|
||||
"source": "测试中…",
|
||||
"kind": "text",
|
||||
@@ -1010,7 +1022,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 110,
|
||||
"line": 176,
|
||||
"column": 105
|
||||
},
|
||||
{
|
||||
@@ -1614,6 +1626,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"4256064de039de89": {
|
||||
"source": "显示粒度",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 185,
|
||||
"column": 19
|
||||
}
|
||||
]
|
||||
},
|
||||
"42655ed8e4108ae2": {
|
||||
"source": "输入(非缓存)",
|
||||
"kind": "text",
|
||||
@@ -1938,7 +1962,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 86,
|
||||
"line": 126,
|
||||
"column": 9
|
||||
},
|
||||
{
|
||||
@@ -1963,6 +1987,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"4f0f45b0312ca4ba": {
|
||||
"source": "1小时",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 49,
|
||||
"column": 37
|
||||
}
|
||||
]
|
||||
},
|
||||
"51d04bc3d286f018": {
|
||||
"source": "近1自然日",
|
||||
"kind": "text",
|
||||
@@ -1970,7 +2006,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 32,
|
||||
"line": 36,
|
||||
"column": 30
|
||||
}
|
||||
]
|
||||
@@ -1996,7 +2032,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 107,
|
||||
"line": 173,
|
||||
"column": 70
|
||||
}
|
||||
]
|
||||
@@ -2289,6 +2325,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"609640f72d422b57": {
|
||||
"source": "快捷时间范围",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 150,
|
||||
"column": 56
|
||||
}
|
||||
]
|
||||
},
|
||||
"61a4c7bac12dc125": {
|
||||
"source": "异常调用:{count}",
|
||||
"kind": "template",
|
||||
@@ -2353,7 +2401,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 111,
|
||||
"line": 177,
|
||||
"column": 109
|
||||
}
|
||||
]
|
||||
@@ -2387,7 +2435,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "shared/api.ts",
|
||||
"line": 495,
|
||||
"line": 496,
|
||||
"column": 43
|
||||
}
|
||||
]
|
||||
@@ -2538,7 +2586,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 106,
|
||||
"line": 172,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -2714,7 +2762,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "shared/api.ts",
|
||||
"line": 490,
|
||||
"line": 491,
|
||||
"column": 43
|
||||
}
|
||||
]
|
||||
@@ -2956,6 +3004,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"859757ea792a73da": {
|
||||
"source": "30分钟",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 48,
|
||||
"column": 37
|
||||
}
|
||||
]
|
||||
},
|
||||
"864597982c308d72": {
|
||||
"source": "已开启静默启动",
|
||||
"kind": "text",
|
||||
@@ -3425,7 +3485,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 106,
|
||||
"line": 172,
|
||||
"column": 70
|
||||
}
|
||||
]
|
||||
@@ -3506,7 +3566,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 34,
|
||||
"line": 38,
|
||||
"column": 29
|
||||
}
|
||||
]
|
||||
@@ -3933,12 +3993,12 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 88,
|
||||
"line": 128,
|
||||
"column": 28
|
||||
},
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 88,
|
||||
"line": 128,
|
||||
"column": 89
|
||||
},
|
||||
{
|
||||
@@ -4351,7 +4411,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 70,
|
||||
"line": 110,
|
||||
"column": 51
|
||||
}
|
||||
]
|
||||
@@ -4485,7 +4545,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 108,
|
||||
"line": 174,
|
||||
"column": 61
|
||||
}
|
||||
]
|
||||
@@ -4608,7 +4668,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 35,
|
||||
"line": 39,
|
||||
"column": 30
|
||||
}
|
||||
]
|
||||
@@ -4868,7 +4928,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 31,
|
||||
"line": 35,
|
||||
"column": 29
|
||||
}
|
||||
]
|
||||
@@ -4907,6 +4967,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"dbaa1c565cb1f108": {
|
||||
"source": "15分钟",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 47,
|
||||
"column": 37
|
||||
}
|
||||
]
|
||||
},
|
||||
"dbd3596e4a86f3c2": {
|
||||
"source": "配置模型",
|
||||
"kind": "text",
|
||||
@@ -5214,7 +5286,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 33,
|
||||
"line": 37,
|
||||
"column": 36
|
||||
}
|
||||
]
|
||||
@@ -5306,6 +5378,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"e85373502c372776": {
|
||||
"source": "1分钟",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 46,
|
||||
"column": 32
|
||||
}
|
||||
]
|
||||
},
|
||||
"e8b1268c1e3610f2": {
|
||||
"source": "已存在",
|
||||
"kind": "text",
|
||||
@@ -5489,7 +5573,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 107,
|
||||
"line": 173,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -5549,6 +5633,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"f784b165bd3fcb0e": {
|
||||
"source": "近4小时",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 42,
|
||||
"column": 35
|
||||
}
|
||||
]
|
||||
},
|
||||
"f85537d1fd2ef6f6": {
|
||||
"source": "自定义概览筛选",
|
||||
"kind": "text",
|
||||
@@ -5556,7 +5652,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/overview/OverviewTimeRangeFilter.tsx",
|
||||
"line": 96,
|
||||
"line": 136,
|
||||
"column": 19
|
||||
}
|
||||
]
|
||||
@@ -5768,7 +5864,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "features/home/HomePage.tsx",
|
||||
"line": 155,
|
||||
"line": 180,
|
||||
"column": 25
|
||||
}
|
||||
]
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"0e41f8e3d59ec47b": "Storage management",
|
||||
"0e67021ebf0a3580": "Import complete: added {imported} models and skipped {skipped} existing models",
|
||||
"0ec1e85b0c3cfa65": "Call details",
|
||||
"100fad4a0b3ab781": "Last 24 hours",
|
||||
"105a9082c346f958": "Testing…",
|
||||
"124be3f86f197802": "Token usage",
|
||||
"12ae77e6202d063e": "Custom Headers",
|
||||
@@ -108,6 +109,7 @@
|
||||
"402495402ce333b1": "Reinitialize plugins",
|
||||
"40a08e7cf320ae07": "Clear detailed records?",
|
||||
"4125fc7ba333524c": "Default light",
|
||||
"4256064de039de89": "Granularity",
|
||||
"42655ed8e4108ae2": "Input (non-cached)",
|
||||
"42a1d9e5b037c210": "Bytes",
|
||||
"42aa8e01e98c0d8c": "Total duration",
|
||||
@@ -131,6 +133,7 @@
|
||||
"4d99c976beb8827e": "Ready",
|
||||
"4e30d7c9ed2b0eee": "Not set",
|
||||
"4eafa9e925b30bcd": "Custom",
|
||||
"4f0f45b0312ca4ba": "1 hour",
|
||||
"51d04bc3d286f018": "Last calendar day",
|
||||
"51de3bcec137ab1b": "Connectivity tests succeeded for all {count} models",
|
||||
"5228358a6db59fe7": "For example: now, 2026-08-23 18:00",
|
||||
@@ -155,6 +158,7 @@
|
||||
"5f9acfb945229062": "Are you sure you no longer want to see this ad?",
|
||||
"5fd2ec5a6e9b654c": "Total: {cost}",
|
||||
"6078a681a306930d": "Cache write",
|
||||
"609640f72d422b57": "Quick time ranges",
|
||||
"61a4c7bac12dc125": "Failed calls: {count}",
|
||||
"61c7d1f758b647d5": "Call information",
|
||||
"621f63a5f08384ac": "Cache read: {tokens}",
|
||||
@@ -203,6 +207,7 @@
|
||||
"83fcfb4c1f2c1641": "Fetch models",
|
||||
"842b9f11cdd96bda": "Launch at login",
|
||||
"843ac7e15a5047a7": "Confirm legacy model configuration import",
|
||||
"859757ea792a73da": "30 minutes",
|
||||
"864597982c308d72": "Silent start enabled",
|
||||
"86de7c4ee8fa7689": "Sync models",
|
||||
"8716e1344b0daddb": "Cursor official",
|
||||
@@ -339,6 +344,7 @@
|
||||
"da7ae985487c38e6": "Last hour",
|
||||
"daede9881787abe7": "Notes",
|
||||
"db340a9896306d08": "Test cancelled",
|
||||
"dbaa1c565cb1f108": "15 minutes",
|
||||
"dbd3596e4a86f3c2": "Configured models",
|
||||
"ddde16f8839da3ce": "Total requests",
|
||||
"de8184da1ef88d03": "Configured",
|
||||
@@ -369,6 +375,7 @@
|
||||
"e77e3d58b0dcffaa": "Duration",
|
||||
"e825a2a42c22380e": "Model type",
|
||||
"e828bd3a0151edc2": "The local CA must be trusted by the system",
|
||||
"e85373502c372776": "1 minute",
|
||||
"e8b1268c1e3610f2": "Existing",
|
||||
"ea26b760e930a7ca": "Call observability",
|
||||
"eb11e2df1d8ae387": "Provider URL",
|
||||
@@ -388,6 +395,7 @@
|
||||
"f6dc1b1641600dd0": "Importing…",
|
||||
"f78265089144369a": "Plugins",
|
||||
"f78413c36d36f090": "Uses the operating system language by default; unsupported languages fall back to English. Current: {language}",
|
||||
"f784b165bd3fcb0e": "Last 4 hours",
|
||||
"f85537d1fd2ef6f6": "Custom overview filters",
|
||||
"f95ea7f4c063eea7": "Disabled",
|
||||
"f9615d05d8e18595": "Clear {label} filter",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"0e41f8e3d59ec47b": "存储管理",
|
||||
"0e67021ebf0a3580": "导入完成:新增 {imported} 个模型,跳过 {skipped} 个已存在模型",
|
||||
"0ec1e85b0c3cfa65": "调用详情",
|
||||
"100fad4a0b3ab781": "近24小时",
|
||||
"105a9082c346f958": "测试中…",
|
||||
"124be3f86f197802": "Token 消耗",
|
||||
"12ae77e6202d063e": "自定义 Headers",
|
||||
@@ -108,6 +109,7 @@
|
||||
"402495402ce333b1": "重新初始化插件",
|
||||
"40a08e7cf320ae07": "确定要清理详细记录吗?",
|
||||
"4125fc7ba333524c": "默认亮色",
|
||||
"4256064de039de89": "显示粒度",
|
||||
"42655ed8e4108ae2": "输入(非缓存)",
|
||||
"42a1d9e5b037c210": "字节数",
|
||||
"42aa8e01e98c0d8c": "总耗时",
|
||||
@@ -131,6 +133,7 @@
|
||||
"4d99c976beb8827e": "可用",
|
||||
"4e30d7c9ed2b0eee": "不设置",
|
||||
"4eafa9e925b30bcd": "自定义",
|
||||
"4f0f45b0312ca4ba": "1小时",
|
||||
"51d04bc3d286f018": "近1自然日",
|
||||
"51de3bcec137ab1b": "全部 {count} 个模型连通性测试成功",
|
||||
"5228358a6db59fe7": "如:现在、2026-08-23 18:00",
|
||||
@@ -155,6 +158,7 @@
|
||||
"5f9acfb945229062": "你确认不想再看到此广告吗?",
|
||||
"5fd2ec5a6e9b654c": "合计:{cost}",
|
||||
"6078a681a306930d": "缓存写入",
|
||||
"609640f72d422b57": "快捷时间范围",
|
||||
"61a4c7bac12dc125": "异常调用:{count}",
|
||||
"61c7d1f758b647d5": "调用信息",
|
||||
"621f63a5f08384ac": "缓存读取:{tokens}",
|
||||
@@ -203,6 +207,7 @@
|
||||
"83fcfb4c1f2c1641": "获取模型",
|
||||
"842b9f11cdd96bda": "开机启动",
|
||||
"843ac7e15a5047a7": "确认导入旧版模型配置",
|
||||
"859757ea792a73da": "30分钟",
|
||||
"864597982c308d72": "已开启静默启动",
|
||||
"86de7c4ee8fa7689": "同步模型",
|
||||
"8716e1344b0daddb": "Cursor 官方",
|
||||
@@ -339,6 +344,7 @@
|
||||
"da7ae985487c38e6": "近1小时",
|
||||
"daede9881787abe7": "备注",
|
||||
"db340a9896306d08": "测试已取消",
|
||||
"dbaa1c565cb1f108": "15分钟",
|
||||
"dbd3596e4a86f3c2": "配置模型",
|
||||
"ddde16f8839da3ce": "总请求",
|
||||
"de8184da1ef88d03": "已配置",
|
||||
@@ -369,6 +375,7 @@
|
||||
"e77e3d58b0dcffaa": "耗时",
|
||||
"e825a2a42c22380e": "模型类型",
|
||||
"e828bd3a0151edc2": "需要在系统中信任本地 CA",
|
||||
"e85373502c372776": "1分钟",
|
||||
"e8b1268c1e3610f2": "已存在",
|
||||
"ea26b760e930a7ca": "调用观测",
|
||||
"eb11e2df1d8ae387": "上游地址",
|
||||
@@ -388,6 +395,7 @@
|
||||
"f6dc1b1641600dd0": "正在导入…",
|
||||
"f78265089144369a": "插件配置",
|
||||
"f78413c36d36f090": "默认跟随操作系统;不支持的系统语言使用英文。当前:{language}",
|
||||
"f784b165bd3fcb0e": "近4小时",
|
||||
"f85537d1fd2ef6f6": "自定义概览筛选",
|
||||
"f95ea7f4c063eea7": "未启用",
|
||||
"f9615d05d8e18595": "清除{label}筛选",
|
||||
|
||||
@@ -462,12 +462,13 @@ export const api = {
|
||||
deleteModel: (hash: string) => request<void>(`/models/${hash}`, { method: "DELETE" }),
|
||||
testModel: (hash: string, testId: string, signal?: AbortSignal) => request<ModelConnectivityResult>(`/models/${encodeURIComponent(hash)}/test/${encodeURIComponent(testId)}`, { method: "POST", signal }),
|
||||
cancelModelTest: (hash: string, testId: string) => request<void>(`/models/${encodeURIComponent(hash)}/test/${encodeURIComponent(testId)}`, { method: "DELETE" }),
|
||||
overview: (filter?: { startMs: number; endMs: number; modelHashes?: string[] }) => {
|
||||
overview: (filter?: { startMs: number; endMs: number; modelHashes?: string[]; bucketMs?: number }) => {
|
||||
const params = new URLSearchParams();
|
||||
if (filter) {
|
||||
params.set("start_ms", String(filter.startMs));
|
||||
params.set("end_ms", String(filter.endMs));
|
||||
if (filter.modelHashes?.length) params.set("model_hashes", JSON.stringify(filter.modelHashes));
|
||||
if (filter.bucketMs) params.set("bucket_ms", String(filter.bucketMs));
|
||||
}
|
||||
const query = params.toString();
|
||||
return request<Overview>(`/overview${query ? `?${query}` : ""}`);
|
||||
|
||||
Reference in New Issue
Block a user