diff --git a/src/app/(dashboard)/dashboard/settings/components/ProxyTab.tsx b/src/app/(dashboard)/dashboard/settings/components/ProxyTab.tsx index 355f2d1909..fe43aa2f2c 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ProxyTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ProxyTab.tsx @@ -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(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 ( <>
@@ -139,53 +78,6 @@ export default function ProxyTab() { - - -
-
-

{t("debugToggle")}

-
- -
-
- -
-
-

Usage Token Buffer

-

- 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. -

-
-
- 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} - /> - - {usageTokenBuffer !== null && parseInt(bufferInput, 10) !== usageTokenBuffer && ( - Current: {usageTokenBuffer} - )} -
-
-
-
(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() { )} + + {/* Debug Mode */} +
+
+
+ +
+

{t("debugToggle")}

+
+
+ +
+
+ + {/* Usage Token Buffer */} +
+
+
+ +
+

Usage Token Buffer

+

+ Extra tokens added to reported usage to account for system prompt overhead. Set to 0 + to report raw provider token counts. Default: 2000. +

+
+
+
+ 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} + /> + + {usageTokenBuffer !== null && parseInt(bufferInput, 10) !== usageTokenBuffer && ( + Current: {usageTokenBuffer} + )} +
+
+
); } diff --git a/src/app/(dashboard)/dashboard/settings/page.tsx b/src/app/(dashboard)/dashboard/settings/page.tsx index 639afd6cc5..ccc4369dcf 100644 --- a/src/app/(dashboard)/dashboard/settings/page.tsx +++ b/src/app/(dashboard)/dashboard/settings/page.tsx @@ -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" && (
+
)} diff --git a/src/app/api/settings/oneproxy/route.ts b/src/app/api/settings/oneproxy/route.ts index a1d938022c..61be15b9a3 100644 --- a/src/app/api/settings/oneproxy/route.ts +++ b/src/app/api/settings/oneproxy/route.ts @@ -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 {