fix: clean up proxy page redundancy and fix 1proxy sync empty body error (#2052)

Integrated into release/v3.8.0
This commit is contained in:
Paijo
2026-05-09 03:34:47 +07:00
committed by GitHub
parent ad966b15f2
commit 56ccf4f8dd
4 changed files with 145 additions and 118 deletions

View File

@@ -1,11 +1,9 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { Card, Button, ProxyConfigModal, Toggle } from "@/shared/components";
import { Card, Button, ProxyConfigModal } from "@/shared/components";
import { useTranslations } from "next-intl";
import ProxyRegistryManager from "./ProxyRegistryManager";
import OneproxyTab from "./OneproxyTab";
import RequestLimitsTab from "./RequestLimitsTab";
export default function ProxyTab() {
const [proxyModalOpen, setProxyModalOpen] = useState(false);
@@ -13,11 +11,6 @@ export default function ProxyTab() {
const mountedRef = useRef(true);
const t = useTranslations("settings");
const tc = useTranslations("common");
const [debugMode, setDebugMode] = useState(false);
const [usageTokenBuffer, setUsageTokenBuffer] = useState<number | null>(null);
const [bufferInput, setBufferInput] = useState("");
const [bufferSaving, setBufferSaving] = useState(false);
const [loading, setLoading] = useState(true);
const loadGlobalProxy = async () => {
try {
@@ -29,44 +22,6 @@ export default function ProxyTab() {
} catch {}
};
const updateDebugMode = async (value: boolean) => {
const previousValue = debugMode;
setDebugMode(value);
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ debugMode: value }),
});
if (!res.ok) {
setDebugMode(previousValue);
}
} catch (err) {
setDebugMode(previousValue);
console.error("Failed to update debugMode:", err);
}
};
const updateUsageTokenBuffer = async () => {
const val = parseInt(bufferInput, 10);
if (isNaN(val) || val < 0 || val > 50000) return;
setBufferSaving(true);
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ usageTokenBuffer: val }),
});
if (res.ok) {
setUsageTokenBuffer(val);
}
} catch (err) {
console.error("Failed to update usageTokenBuffer:", err);
} finally {
setBufferSaving(false);
}
};
useEffect(() => {
mountedRef.current = true;
async function init() {
@@ -85,22 +40,6 @@ export default function ProxyTab() {
};
}, []);
useEffect(() => {
fetch("/api/settings", { cache: "no-store" })
.then((res) => {
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
return res.json();
})
.then((data) => {
setDebugMode(data.debugMode === true);
const buf = typeof data.usageTokenBuffer === "number" ? data.usageTokenBuffer : 2000;
setUsageTokenBuffer(buf);
setBufferInput(String(buf));
setLoading(false);
})
.catch(() => setLoading(false));
}, []);
return (
<>
<div className="flex flex-col gap-6">
@@ -139,53 +78,6 @@ export default function ProxyTab() {
</Card>
<ProxyRegistryManager />
<OneproxyTab />
<Card className="p-6 mt-4">
<div className="flex items-center justify-between">
<div>
<p className="font-medium">{t("debugToggle")}</p>
</div>
<Toggle checked={debugMode} onChange={updateDebugMode} disabled={loading} />
</div>
</Card>
<Card className="p-6 mt-4">
<div className="flex flex-col gap-3">
<div>
<p className="font-medium">Usage Token Buffer</p>
<p className="text-sm text-text-muted mt-1">
Extra tokens added to reported usage to account for system prompt overhead. Set to 0
to report raw provider token counts. Default: 2000. Changes take effect within 30
seconds.
</p>
</div>
<div className="flex items-center gap-3">
<input
type="number"
min={0}
max={50000}
value={bufferInput}
onChange={(e) => setBufferInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") updateUsageTokenBuffer();
}}
className="w-32 px-3 py-1.5 rounded bg-surface-2 border border-border text-sm text-text-primary"
disabled={loading}
/>
<Button
size="sm"
variant="primary"
onClick={updateUsageTokenBuffer}
disabled={bufferSaving || loading || parseInt(bufferInput, 10) === usageTokenBuffer}
>
{bufferSaving ? tc("saving") : tc("save")}
</Button>
{usageTokenBuffer !== null && parseInt(bufferInput, 10) !== usageTokenBuffer && (
<span className="text-xs text-text-muted">Current: {usageTokenBuffer}</span>
)}
</div>
</div>
</Card>
<RequestLimitsTab />
</div>
<ProxyConfigModal

View File

@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { Card, Button, Badge } from "@/shared/components";
import { Card, Button, Badge, Toggle } from "@/shared/components";
import { useLocale, useTranslations } from "next-intl";
const rowCountFormatter = new Intl.NumberFormat("en-US");
@@ -73,6 +73,11 @@ export default function SystemStorageTab() {
const [dbSettingsLoading, setDbSettingsLoading] = useState(true);
const [dbSettingsSaving, setDbSettingsSaving] = useState(false);
const [dbStatsRefreshing, setDbStatsRefreshing] = useState(false);
const [debugMode, setDebugMode] = useState(false);
const [usageTokenBuffer, setUsageTokenBuffer] = useState<number | null>(null);
const [bufferInput, setBufferInput] = useState("");
const [bufferSaving, setBufferSaving] = useState(false);
const [generalLoading, setGeneralLoading] = useState(true);
const loadBackups = async () => {
setBackupsLoading(true);
@@ -245,8 +250,65 @@ export default function SystemStorageTab() {
useEffect(() => {
loadStorageHealth();
loadDatabaseSettings();
loadGeneralSettings();
}, []);
const loadGeneralSettings = async () => {
setGeneralLoading(true);
try {
const res = await fetch("/api/settings", { cache: "no-store" });
if (res.ok) {
const data = await res.json();
setDebugMode(data.debugMode === true);
const buf = typeof data.usageTokenBuffer === "number" ? data.usageTokenBuffer : 2000;
setUsageTokenBuffer(buf);
setBufferInput(String(buf));
}
} catch {
// ignore
} finally {
setGeneralLoading(false);
}
};
const updateDebugMode = async (value: boolean) => {
const previousValue = debugMode;
setDebugMode(value);
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ debugMode: value }),
});
if (!res.ok) {
setDebugMode(previousValue);
}
} catch (err) {
setDebugMode(previousValue);
console.error("Failed to update debugMode:", err);
}
};
const updateUsageTokenBuffer = async () => {
const val = parseInt(bufferInput, 10);
if (isNaN(val) || val < 0 || val > 50000) return;
setBufferSaving(true);
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ usageTokenBuffer: val }),
});
if (res.ok) {
setUsageTokenBuffer(val);
}
} catch (err) {
console.error("Failed to update usageTokenBuffer:", err);
} finally {
setBufferSaving(false);
}
};
/** Triggers a browser file download from an existing Blob. */
const triggerDownload = (blob: Blob, filename: string) => {
const url = URL.createObjectURL(blob);
@@ -1774,6 +1836,72 @@ export default function SystemStorageTab() {
</div>
</div>
)}
{/* Debug Mode */}
<div className="mt-6 pt-3 border-t border-border/50">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span
className="material-symbols-outlined text-[18px] text-text-muted"
aria-hidden="true"
>
bug_report
</span>
<div>
<p className="font-medium">{t("debugToggle")}</p>
</div>
</div>
<Toggle checked={debugMode} onChange={updateDebugMode} disabled={generalLoading} />
</div>
</div>
{/* Usage Token Buffer */}
<div className="mt-4 pt-3 border-t border-border/50">
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
<span
className="material-symbols-outlined text-[18px] text-text-muted"
aria-hidden="true"
>
pin
</span>
<div>
<p className="font-medium">Usage Token Buffer</p>
<p className="text-sm text-text-muted mt-1">
Extra tokens added to reported usage to account for system prompt overhead. Set to 0
to report raw provider token counts. Default: 2000.
</p>
</div>
</div>
<div className="flex items-center gap-3">
<input
type="number"
min={0}
max={50000}
value={bufferInput}
onChange={(e) => setBufferInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") updateUsageTokenBuffer();
}}
className="w-32 px-3 py-1.5 rounded bg-surface-2 border border-border text-sm text-text-primary"
disabled={generalLoading}
/>
<Button
size="sm"
variant="primary"
onClick={updateUsageTokenBuffer}
disabled={
bufferSaving || generalLoading || parseInt(bufferInput, 10) === usageTokenBuffer
}
>
{bufferSaving ? tc("saving") : tc("save")}
</Button>
{usageTokenBuffer !== null && parseInt(bufferInput, 10) !== usageTokenBuffer && (
<span className="text-xs text-text-muted">Current: {usageTokenBuffer}</span>
)}
</div>
</div>
</div>
</Card>
);
}

View File

@@ -21,6 +21,7 @@ import ResilienceTab from "./components/ResilienceTab";
import CliproxyapiSettingsTab from "./components/CliproxyapiSettingsTab";
import PayloadRulesTab from "./components/PayloadRulesTab";
import VisionBridgeSettingsTab from "./components/VisionBridgeSettingsTab";
import RequestLimitsTab from "./components/RequestLimitsTab";
import ModelRoutingSection from "@/shared/components/ModelRoutingSection";
const tabs = [
@@ -136,6 +137,7 @@ export default function SettingsPage() {
{activeTab === "advanced" && (
<div className="flex flex-col gap-6">
<PayloadRulesTab />
<RequestLimitsTab />
<CliproxyapiSettingsTab />
</div>
)}

View File

@@ -65,14 +65,19 @@ export async function POST(request: Request) {
if (authError) return authError;
let rawBody: unknown;
try {
rawBody = await request.json();
} catch {
return createErrorResponse({
status: 400,
message: "Invalid JSON body",
type: "invalid_request",
});
const contentType = request.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
try {
rawBody = await request.json();
} catch {
return createErrorResponse({
status: 400,
message: "Invalid JSON body",
type: "invalid_request",
});
}
} else {
rawBody = {};
}
try {