diff --git a/next.config.mjs b/next.config.mjs index 3853b4b00bb..06754f11dca 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -11,8 +11,15 @@ const proxyClientMaxBodySize = process.env.NINEROUTER_PROXY_CLIENT_MAX_BODY_SIZE /** @type {import('next').NextConfig} */ const nextConfig = { + reactStrictMode: true, + output: 'standalone', distDir: process.env.NEXT_DIST_DIR || ".next", - output: "standalone", + // basePath is required for middleware to receive request.nextUrl.basePath, + // for client-side navigation (router.push, Link) to auto-prepend the prefix, + // and for NEXT_PUBLIC_BASE_PATH to be inlined at build time. + // assetPrefix only prefixes static asset URLs and does NOT populate basePath in middleware. + basePath: process.env.NINEROUTER_BASE_PATH || "", + serverExternalPackages: ["better-sqlite3", "sql.js", "node:sqlite", "bun:sqlite"], turbopack: { root: tracingRoot @@ -24,7 +31,9 @@ const nextConfig = { images: { unoptimized: true }, - env: {}, + env: { + NEXT_PUBLIC_BASE_PATH: process.env.NINEROUTER_BASE_PATH || "", + }, experimental: { // #1529/#1572: LLM clients can send long context or base64 image payloads through /v1 rewrites. proxyClientMaxBodySize, diff --git a/package.json b/package.json index e252a76803a..6c31682ff7d 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,13 @@ "description": "9Router web dashboard", "private": true, "scripts": { - "dev": "next dev --webpack --port 20128", - "build": "next build --webpack", + "dev": "next dev --port 20128", + "prebuild": "bash scripts/prebuild.sh", + "build": "next build", + "postbuild": "bash scripts/postbuild.sh", "start": "next start", - "dev:bun": "bun --bun next dev --webpack --port 20128", - "build:bun": "bun --bun next build --webpack", + "dev:bun": "bun --bun next dev --port 20128", + "build:bun": "bun --bun next build", "start:bun": "bun ./.next/standalone/server.js" }, "dependencies": { @@ -27,7 +29,7 @@ "marked": "^18.0.1", "material-symbols": "^0.44.6", "monaco-editor": "^0.55.1", - "next": "^16.1.6", + "next": "16.2.8", "node-forge": "^1.3.3", "node-machine-id": "^1.1.12", "open": "^11.0.0", diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000000..483f378543c --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/postcss.config.mjs b/postcss.config.mjs.bak similarity index 100% rename from postcss.config.mjs rename to postcss.config.mjs.bak diff --git a/postcss.config.mjs.original b/postcss.config.mjs.original new file mode 100644 index 00000000000..17fa4232a29 --- /dev/null +++ b/postcss.config.mjs.original @@ -0,0 +1,12 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const projectRoot = path.dirname(fileURLToPath(import.meta.url)); + +export default { + plugins: { + "@tailwindcss/postcss": { + base: projectRoot, + }, + }, +}; diff --git a/scripts/postbuild.sh b/scripts/postbuild.sh new file mode 100755 index 00000000000..d2e5ba81494 --- /dev/null +++ b/scripts/postbuild.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Fix the standalone static symlink after rebuild +# The rebuild destroys the symlink; this recreates it unconditionally. +# Also copies proxy/middleware manifest files (REQUIRED for proxy registration in production) +set -e + +STANDALONE_DIR=".next/standalone" +MAIN_STATIC=".next/static" +STANDALONE_STATIC="${STANDALONE_DIR}/.next/static" + +if [ -d "${STANDALONE_DIR}" ]; then + # Remove stale symlink or directory if present + if [ -L "${STANDALONE_STATIC}" ] || [ -d "${STANDALONE_STATIC}" ]; then + rm -rf "${STANDALONE_STATIC}" + fi + ln -sf "$(pwd)/${MAIN_STATIC}" "$(pwd)/${STANDALONE_STATIC}" + echo "Created symlink: ${STANDALONE_STATIC} -> $(pwd)/${MAIN_STATIC}" +fi + +# Copy proxy/middleware manifest files to standalone (REQUIRED for proxy registration) +# Next.js 16 uses .next/server/middleware/ subdirectory for the manifest +mkdir -p "${STANDALONE_DIR}/.next/server/middleware" 2>/dev/null || true + +# Next.js 16 (Turbopack) — proxy manifest at .next/server/middleware/ +if [ -f ".next/server/middleware/middleware-manifest.json" ]; then + cp .next/server/middleware/middleware-manifest.json "${STANDALONE_DIR}/.next/server/middleware/middleware-manifest.json" + echo "Copied Next.js 16 proxy manifest (middleware/ subdirectory)" +fi + +# Legacy / webpack — middleware manifest at .next/server/ root +if [ -f ".next/server/middleware-manifest.json" ]; then + cp .next/server/middleware-manifest.json "${STANDALONE_DIR}/.next/server/middleware-manifest.json" 2>/dev/null || true + cp .next/server/middleware-build-manifest.js "${STANDALONE_DIR}/.next/server/middleware-build-manifest.js" 2>/dev/null || true + cp .next/server/middleware.js "${STANDALONE_DIR}/.next/server/middleware.js" 2>/dev/null || true + echo "Copied legacy middleware manifest files to standalone" +fi + +# Also copy proxy manifest if present +if [ -f ".next/server/proxy-manifest.json" ]; then + mkdir -p "${STANDALONE_DIR}/.next/server/proxy" 2>/dev/null || true + cp .next/server/proxy-manifest.json "${STANDALONE_DIR}/.next/server/proxy/proxy-manifest.json" 2>/dev/null || true + echo "Copied proxy manifest to standalone" +fi + +# Copy edge chunks (required for proxy runtime) +if [ -d ".next/server/edge/chunks" ]; then + mkdir -p "${STANDALONE_DIR}/.next/server/edge/chunks" + cp -r .next/server/edge/chunks/* "${STANDALONE_DIR}/.next/server/edge/chunks/" 2>/dev/null || true + echo "Copied edge chunks to standalone" +fi + +echo "Post-build complete" diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh new file mode 100755 index 00000000000..fdf845df850 --- /dev/null +++ b/scripts/prebuild.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Ensure SQLite WAL files exist for the standalone build copy +set -e +DB_DIR="data/db" +mkdir -p "${DB_DIR}" +for f in data.sqlite-shm data.sqlite-wal; do + if [ ! -f "${DB_DIR}/${f}" ]; then + touch "${DB_DIR}/${f}" + fi +done diff --git a/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js b/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js index a97d0a5ea1c..0521cfed5e3 100644 --- a/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js +++ b/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js @@ -218,7 +218,7 @@ export default function BasicChatPageClient() { setLoadError(""); try { - const providersRes = await fetch("/api/providers", { cache: "no-store" }); + const providersRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { cache: "no-store" }); const providersData = await providersRes.json().catch(() => ({})); const connections = Array.isArray(providersData.connections) ? providersData.connections.filter((connection) => connection?.isActive !== false) @@ -636,7 +636,7 @@ export default function BasicChatPageClient() { })); try { - const response = await fetch("/api/dashboard/chat/completions", { + const response = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/dashboard/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", diff --git a/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js b/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js index fa8d7111487..a850c5fe31b 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js +++ b/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js @@ -31,10 +31,10 @@ export default function ToolDetailClient({ toolId, machineId }) { (async () => { try { const [provRes, settingsRes, tunnelRes, keysRes] = await Promise.all([ - fetch("/api/providers"), - fetch("/api/settings"), - fetch("/api/tunnel/status"), - fetch("/api/keys"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/status"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/keys"), ]); if (!mounted) return; if (provRes.ok) { diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js index 48962ccae0b..6e3a89c9ef0 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js @@ -51,7 +51,7 @@ export default function AntigravityToolCard({ const loadSavedMappings = async () => { try { - const res = await fetch("/api/cli-tools/antigravity-mitm/alias?tool=antigravity"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm/alias?tool=antigravity"); if (res.ok) { const data = await res.json(); const aliases = data.aliases || {}; @@ -67,7 +67,7 @@ export default function AntigravityToolCard({ const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -77,7 +77,7 @@ export default function AntigravityToolCard({ const fetchStatus = async () => { try { - const res = await fetch("/api/cli-tools/antigravity-mitm"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm"); if (res.ok) { const data = await res.json(); setStatus(data); @@ -120,7 +120,7 @@ export default function AntigravityToolCard({ || (apiKeys?.length > 0 ? apiKeys[0].key : null) || (!cloudEnabled ? "sk_9router" : null); - const res = await fetch("/api/cli-tools/antigravity-mitm", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ apiKey: keyToUse, sudoPassword: password }), @@ -149,7 +149,7 @@ export default function AntigravityToolCard({ setLoading(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/antigravity-mitm", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sudoPassword: password }), @@ -209,7 +209,7 @@ export default function AntigravityToolCard({ setMessage(null); try { - const res = await fetch("/api/cli-tools/antigravity-mitm/alias", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm/alias", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ tool: "antigravity", mappings: modelMappings }), diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js index c276fcb91cc..e6a7e91c190 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js @@ -70,7 +70,7 @@ export default function ClaudeToolCard({ }, [isExpanded]); useEffect(() => { - fetch("/api/settings").then(r => r.json()).then(data => { + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings").then(r => r.json()).then(data => { setCcFilterNaming(!!data.ccFilterNaming); }).catch(() => {}); }, []); @@ -78,7 +78,7 @@ export default function ClaudeToolCard({ const handleCcFilterNamingToggle = async (e) => { const value = e.target.checked; setCcFilterNaming(value); - await fetch("/api/settings", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ccFilterNaming: value }), @@ -87,7 +87,7 @@ export default function ClaudeToolCard({ const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -120,7 +120,7 @@ export default function ClaudeToolCard({ const checkClaudeStatus = async () => { setCheckingClaude(true); try { - const res = await fetch("/api/cli-tools/claude-settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/claude-settings"); const data = await res.json(); setClaudeStatus(data); } catch (error) { @@ -159,7 +159,7 @@ export default function ClaudeToolCard({ const targetModel = modelMappings[model.alias]; if (targetModel && model.envKey) env[model.envKey] = targetModel; }); - const res = await fetch("/api/cli-tools/claude-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/claude-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ env }), @@ -182,7 +182,7 @@ export default function ClaudeToolCard({ setRestoring(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/claude-settings", { method: "DELETE" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/claude-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js index 41fd0b11835..f8c84039eeb 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js @@ -43,7 +43,7 @@ export default function ClineToolCard({ tool, isExpanded, onToggle, baseUrl, api const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -70,7 +70,7 @@ export default function ClineToolCard({ tool, isExpanded, onToggle, baseUrl, api const checkStatus = async () => { setChecking(true); try { - const res = await fetch("/api/cli-tools/cline-settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/cline-settings"); const data = await res.json(); setStatus(data); } catch (error) { @@ -88,7 +88,7 @@ export default function ClineToolCard({ tool, isExpanded, onToggle, baseUrl, api ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); - const res = await fetch("/api/cli-tools/cline-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/cline-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, model: selectedModel }), @@ -111,7 +111,7 @@ export default function ClineToolCard({ tool, isExpanded, onToggle, baseUrl, api setRestoring(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/cline-settings", { method: "DELETE" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/cline-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js index 12ef389ec45..45b66b6309a 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js @@ -43,7 +43,7 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -84,7 +84,7 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api const checkCodexStatus = async () => { setCheckingCodex(true); try { - const res = await fetch("/api/cli-tools/codex-settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/codex-settings"); const data = await res.json(); setCodexStatus(data); } catch (error) { @@ -103,7 +103,7 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); - const res = await fetch("/api/cli-tools/codex-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/codex-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -131,7 +131,7 @@ export default function CodexToolCard({ tool, isExpanded, onToggle, baseUrl, api setRestoring(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/codex-settings", { method: "DELETE" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/codex-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js index 5987688d1d5..29b9f2bec70 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js @@ -55,7 +55,7 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -68,7 +68,7 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a const keyToUse = (selectedApiKey && selectedApiKey.trim()) ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); - await fetch("/api/cli-tools/copilot-settings", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/copilot-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, models }), @@ -99,7 +99,7 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a const checkStatus = async () => { setChecking(true); try { - const res = await fetch("/api/cli-tools/copilot-settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/copilot-settings"); const data = await res.json(); setStatus(data); } catch (error) { @@ -117,7 +117,7 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); - const res = await fetch("/api/cli-tools/copilot-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/copilot-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, models: selectedModels }), @@ -140,7 +140,7 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a setRestoring(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/copilot-settings", { method: "DELETE" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/copilot-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js index a3b20bcc446..1db9b0a6873 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js @@ -66,7 +66,7 @@ export default function CoworkToolCard({ useEffect(() => { if (!isExpanded) return; - fetch("/api/models/alias") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias") .then((r) => r.ok ? r.json() : null) .then((data) => { if (data) setModelAliases(data.aliases || {}); @@ -162,7 +162,7 @@ export default function CoworkToolCard({ const handleCreateCombo = async ({ name, models }) => { try { - const res = await fetch("/api/combos", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/combos", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, models }), diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js index 426ca7425a3..3a611e97e5f 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js @@ -67,7 +67,7 @@ export default function DeepSeekTuiToolCard({ const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js index 7e8e2eac7d7..9aabe219eda 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js @@ -69,7 +69,7 @@ export default function DroidToolCard({ const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -100,7 +100,7 @@ export default function DroidToolCard({ const checkDroidStatus = async () => { setCheckingDroid(true); try { - const res = await fetch("/api/cli-tools/droid-settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/droid-settings"); const data = await res.json(); setDroidStatus(data); } catch (error) { @@ -143,7 +143,7 @@ export default function DroidToolCard({ || (apiKeys?.length > 0 ? apiKeys[0].key : null) || (!cloudEnabled ? "sk_9router" : null); - const res = await fetch("/api/cli-tools/droid-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/droid-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -171,7 +171,7 @@ export default function DroidToolCard({ setRestoring(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/droid-settings", { method: "DELETE" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/droid-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js index 806be6bb06e..971b2818baa 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js @@ -67,7 +67,7 @@ export default function HermesToolCard({ const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js index 487def9b207..7d1a2488ce8 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js @@ -65,7 +65,7 @@ export default function JcodeToolCard({ const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -93,7 +93,7 @@ export default function JcodeToolCard({ const checkJcodeStatus = async () => { setCheckingJcode(true); try { - const res = await fetch("/api/cli-tools/jcode-settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/jcode-settings"); const data = await res.json(); setJcodeStatus(data); } catch (error) { @@ -130,7 +130,7 @@ export default function JcodeToolCard({ || (apiKeys?.length > 0 ? apiKeys[0].key : null) || (!cloudEnabled ? "sk_9router" : null); - const res = await fetch("/api/cli-tools/jcode-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/jcode-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -157,7 +157,7 @@ export default function JcodeToolCard({ setRestoring(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/jcode-settings", { method: "DELETE" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/jcode-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js index 2a8a074e92f..c07ce8ca20d 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js @@ -39,7 +39,7 @@ export default function KiloToolCard({ tool, isExpanded, onToggle, baseUrl, apiK const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -64,7 +64,7 @@ export default function KiloToolCard({ tool, isExpanded, onToggle, baseUrl, apiK const checkStatus = async () => { setChecking(true); try { - const res = await fetch("/api/cli-tools/kilo-settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/kilo-settings"); const data = await res.json(); setStatus(data); } catch (error) { @@ -82,7 +82,7 @@ export default function KiloToolCard({ tool, isExpanded, onToggle, baseUrl, apiK ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); - const res = await fetch("/api/cli-tools/kilo-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/kilo-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ baseUrl: getEffectiveBaseUrl(), apiKey: keyToUse, model: selectedModel }), @@ -105,7 +105,7 @@ export default function KiloToolCard({ tool, isExpanded, onToggle, baseUrl, apiK setRestoring(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/kilo-settings", { method: "DELETE" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/kilo-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js index 8b2ab4167c8..5921dced877 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js @@ -29,7 +29,7 @@ export default function MitmServerCard({ apiKeys, cloudEnabled, onStatusChange } const fetchStatus = useCallback(async () => { try { - const res = await fetch("/api/cli-tools/antigravity-mitm"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm"); if (res.ok) { const data = await res.json(); setStatus(data); @@ -68,7 +68,7 @@ export default function MitmServerCard({ apiKeys, cloudEnabled, onStatusChange } try { let res; if (action === "trust-cert") { - res = await fetch("/api/cli-tools/antigravity-mitm", { + res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "trust-cert", sudoPassword: password }), @@ -77,7 +77,7 @@ export default function MitmServerCard({ apiKeys, cloudEnabled, onStatusChange } const keyToUse = selectedApiKey?.trim() || (apiKeys?.length > 0 ? apiKeys[0].key : null) || (!cloudEnabled ? "sk_9router" : null); - res = await fetch("/api/cli-tools/antigravity-mitm", { + res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -88,7 +88,7 @@ export default function MitmServerCard({ apiKeys, cloudEnabled, onStatusChange } }), }); } else { - res = await fetch("/api/cli-tools/antigravity-mitm", { + res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sudoPassword: password }), diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js index 82384292c30..af3d5d59665 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js @@ -56,7 +56,7 @@ export default function MitmToolCard({ const saveMappings = useCallback(async (mappings) => { try { - await fetch("/api/cli-tools/antigravity-mitm/alias", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm/alias", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ tool: tool.id, mappings }), @@ -100,7 +100,7 @@ export default function MitmToolCard({ setLoading(true); setWarning(null); try { - const res = await fetch("/api/cli-tools/antigravity-mitm", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/antigravity-mitm", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ tool: tool.id, action, sudoPassword: password }), diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js index 88a73c63510..7feb31a97eb 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js @@ -66,7 +66,7 @@ export default function OpenClawToolCard({ const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -98,7 +98,7 @@ export default function OpenClawToolCard({ const checkOpenclawStatus = async () => { setCheckingOpenclaw(true); try { - const res = await fetch("/api/cli-tools/openclaw-settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/openclaw-settings"); const data = await res.json(); setOpenclawStatus(data); } catch (error) { @@ -135,7 +135,7 @@ export default function OpenClawToolCard({ || (apiKeys?.length > 0 ? apiKeys[0].key : null) || (!cloudEnabled ? "sk_9router" : null); - const res = await fetch("/api/cli-tools/openclaw-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/openclaw-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -163,7 +163,7 @@ export default function OpenClawToolCard({ setRestoring(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/openclaw-settings", { method: "DELETE" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/openclaw-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js index 9a4a4b84046..72cf09d3d70 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js @@ -65,7 +65,7 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, const fetchModelAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) setModelAliases(data.aliases || {}); } catch (error) { @@ -79,7 +79,7 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); const validActiveModel = models.includes(activeModel) ? activeModel : (models[0] || ""); - await fetch("/api/cli-tools/opencode-settings", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/opencode-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -115,7 +115,7 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, const checkStatus = async () => { setChecking(true); try { - const res = await fetch("/api/cli-tools/opencode-settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/opencode-settings"); const data = await res.json(); setStatus(data); } catch (error) { @@ -133,7 +133,7 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, ? selectedApiKey : (!cloudEnabled ? "sk_9router" : selectedApiKey); - const res = await fetch("/api/cli-tools/opencode-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/opencode-settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -162,7 +162,7 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, setRestoring(true); setMessage(null); try { - const res = await fetch("/api/cli-tools/opencode-settings", { method: "DELETE" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/opencode-settings", { method: "DELETE" }); const data = await res.json(); if (res.ok) { setMessage({ type: "success", text: "Settings reset successfully!" }); @@ -334,7 +334,7 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, onClick={async () => { if (model === activeModel) { try { - const res = await fetch("/api/cli-tools/opencode-settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/cli-tools/opencode-settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clearActiveModel: true }), diff --git a/src/app/(dashboard)/dashboard/combos/page.js b/src/app/(dashboard)/dashboard/combos/page.js index 629432c6ebd..906ee965159 100644 --- a/src/app/(dashboard)/dashboard/combos/page.js +++ b/src/app/(dashboard)/dashboard/combos/page.js @@ -29,9 +29,9 @@ export default function CombosPage() { const fetchData = async () => { try { const [combosRes, providersRes, settingsRes] = await Promise.all([ - fetch("/api/combos"), - fetch("/api/providers"), - fetch("/api/settings"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/combos"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings"), ]); const combosData = await combosRes.json(); const providersData = await providersRes.json(); @@ -52,7 +52,7 @@ export default function CombosPage() { const handleCreate = async (data) => { try { - const res = await fetch("/api/combos", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/combos", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), @@ -115,7 +115,7 @@ export default function CombosPage() { delete updated[comboName]; } - await fetch("/api/settings", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ comboStrategies: updated }), @@ -415,7 +415,7 @@ function ComboFormModal({ isOpen, combo, onClose, onSave, activeProviders, kindF const fetchModalData = async () => { try { - const aliasesRes = await fetch("/api/models/alias"); + const aliasesRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); if (!aliasesRes.ok) return; const aliasesData = await aliasesRes.json(); setModelAliases(aliasesData.aliases || {}); diff --git a/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js b/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js index 6373015d1f2..aab1bdeb095 100644 --- a/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js +++ b/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js @@ -26,7 +26,7 @@ export default function ConsoleLogClient() { const handleClear = async () => { try { - await fetch("/api/translator/console-logs", { method: "DELETE" }); + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/translator/console-logs", { method: "DELETE" }); // UI cleared via SSE "clear" event } catch (err) { console.error("Failed to clear console logs:", err); @@ -34,7 +34,8 @@ export default function ConsoleLogClient() { }; useEffect(() => { - const es = new EventSource("/api/translator/console-logs/stream"); + const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || ""; + const es = new EventSource(BASE_PATH + "/api/translator/console-logs/stream"); es.onopen = () => setConnected(true); diff --git a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js index 71e6012a80c..97acf56d5de 100644 --- a/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js +++ b/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js @@ -241,7 +241,7 @@ export default function APIPageClient({ machineId }) { // Trust user intent (settingsEnabled): UI stays "enabled" while watchdog restarts process const syncTunnelStatus = async () => { try { - const statusRes = await fetch("/api/tunnel/status", { cache: "no-store" }); + const statusRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/status", { cache: "no-store" }); if (!statusRes.ok) return; const data = await statusRes.json(); const tEnabled = data.tunnel?.settingsEnabled ?? data.tunnel?.enabled ?? false; @@ -263,8 +263,8 @@ export default function APIPageClient({ machineId }) { setTunnelChecking(true); try { const [settingsRes, statusRes] = await Promise.all([ - fetch("/api/settings"), - fetch("/api/tunnel/status", { cache: "no-store" }) + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/status", { cache: "no-store" }) ]); if (settingsRes.ok) { const data = await settingsRes.json(); @@ -300,7 +300,7 @@ export default function APIPageClient({ machineId }) { const handleTunnelDashboardAccess = async (value) => { try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ tunnelDashboardAccess: value }), @@ -313,7 +313,7 @@ export default function APIPageClient({ machineId }) { const handleRequireApiKey = async (value) => { try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ requireApiKey: value }), @@ -326,7 +326,7 @@ export default function APIPageClient({ machineId }) { const handleRtkEnabled = async (value) => { try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ rtkEnabled: value }), @@ -339,7 +339,7 @@ export default function APIPageClient({ machineId }) { const patchSetting = async (patch) => { try { - await fetch("/api/settings", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(patch), @@ -361,7 +361,7 @@ export default function APIPageClient({ machineId }) { const fetchData = async () => { try { - const keysRes = await fetch("/api/keys"); + const keysRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/keys"); const keysData = await keysRes.json(); if (keysRes.ok) { setKeys(keysData.keys || []); @@ -396,7 +396,7 @@ export default function APIPageClient({ machineId }) { // Every 5 pings (~10s), check if backend process still alive if ((Date.now() - start) % 10000 < TUNNEL_PING_INTERVAL_MS) { try { - const statusRes = await fetch("/api/tunnel/status"); + const statusRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/status"); if (statusRes.ok) { const status = await statusRes.json(); if (!status.tunnel?.enabled) { @@ -426,7 +426,7 @@ export default function APIPageClient({ machineId }) { const pollProgress = async () => { while (polling) { try { - const r = await fetch("/api/tunnel/status"); + const r = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/status"); if (r.ok) { const s = await r.json(); if (s.download?.downloading) { @@ -442,7 +442,7 @@ export default function APIPageClient({ machineId }) { pollProgress(); try { - const res = await fetch("/api/tunnel/enable", { method: "POST" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/enable", { method: "POST" }); polling = false; const data = await res.json(); if (!res.ok) { @@ -472,7 +472,7 @@ export default function APIPageClient({ machineId }) { setTunnelLoading(true); setTunnelStatus(null); try { - const res = await fetch("/api/tunnel/disable", { method: "POST" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/disable", { method: "POST" }); const data = await res.json(); if (res.ok) { setTunnelEnabled(false); @@ -493,7 +493,7 @@ export default function APIPageClient({ machineId }) { const checkTailscaleInstalled = async () => { setTsInstalled(null); try { - const res = await fetch("/api/tunnel/tailscale-check"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/tailscale-check"); if (res.ok) { const data = await res.json(); setTsInstalled(data.installed); @@ -509,7 +509,7 @@ export default function APIPageClient({ machineId }) { setTsStatus(null); setTsInstallLog([]); try { - const res = await fetch("/api/tunnel/tailscale-install", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/tailscale-install", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sudoPassword: tsSudoPassword }), @@ -592,7 +592,7 @@ export default function APIPageClient({ machineId }) { setTsProgress("Connecting..."); clearUserAuth(); try { - const res = await fetch("/api/tunnel/tailscale-enable", { method: "POST" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/tailscale-enable", { method: "POST" }); const data = await res.json(); if (res.ok && data.success) { @@ -609,13 +609,13 @@ export default function APIPageClient({ machineId }) { for (let i = 0; i < 40; i++) { await new Promise((r) => setTimeout(r, 3000)); try { - const r2 = await fetch("/api/tunnel/tailscale-check"); + const r2 = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/tailscale-check"); if (r2.ok) { const check = await r2.json(); if (check.loggedIn) { clearUserAuth(); setTsProgress("Starting funnel..."); - const res2 = await fetch("/api/tunnel/tailscale-enable", { method: "POST" }); + const res2 = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/tailscale-enable", { method: "POST" }); const data2 = await res2.json(); if (res2.ok && data2.success) { setTsUrl(data2.tunnelUrl || ""); @@ -659,7 +659,7 @@ export default function APIPageClient({ machineId }) { for (let i = 0; i < 40; i++) { await new Promise((r) => setTimeout(r, 3000)); try { - const res = await fetch("/api/tunnel/tailscale-enable", { method: "POST" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/tailscale-enable", { method: "POST" }); const data = await res.json(); if (res.ok && data.success) { clearUserAuth(); @@ -685,7 +685,7 @@ export default function APIPageClient({ machineId }) { setTsLoading(true); setTsStatus(null); try { - const res = await fetch("/api/tunnel/tailscale-disable", { method: "POST" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/tailscale-disable", { method: "POST" }); const data = await res.json(); if (res.ok) { setTsEnabled(false); @@ -717,7 +717,7 @@ export default function APIPageClient({ machineId }) { if (!newKeyName.trim()) return; try { - const res = await fetch("/api/keys", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/keys", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newKeyName }), diff --git a/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js b/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js index 5382f6f4615..d0e3c6c993b 100644 --- a/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js +++ b/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js @@ -151,11 +151,11 @@ function EmbeddingExampleCard({ providerId, customAlias }) { useEffect(() => { setLocalEndpoint(window.location.origin); - fetch("/api/keys") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/keys") .then((r) => r.json()) .then((d) => { setApiKey((d.keys || []).find((k) => k.isActive !== false)?.key || ""); }) .catch(() => {}); - fetch("/api/tunnel/status") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/status") .then((r) => r.json()) .then((d) => { if (d.publicUrl) setTunnelEndpoint(d.publicUrl); }) .catch(() => {}); @@ -186,7 +186,7 @@ function EmbeddingExampleCard({ providerId, customAlias }) { try { const headers = { "Content-Type": "application/json" }; if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`; - const res = await fetch("/api/v1/embeddings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/v1/embeddings", { method: "POST", headers, body: JSON.stringify(buildBody()), @@ -414,11 +414,11 @@ function TtsExampleCard({ providerId }) { useEffect(() => { setLocalEndpoint(window.location.origin); - fetch("/api/keys") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/keys") .then((r) => r.json()) .then((d) => { setApiKey((d.keys || []).find((k) => k.isActive !== false)?.key || ""); }) .catch(() => {}); - fetch("/api/tunnel/status") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/status") .then((r) => r.json()) .then((d) => { if (d.publicUrl) setTunnelEndpoint(d.publicUrl); }) .catch(() => {}); @@ -958,16 +958,16 @@ function GenericExampleCard({ providerId, kind }) { useEffect(() => { setLocalEndpoint(window.location.origin); - fetch("/api/keys") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/keys") .then((r) => r.json()) .then((d) => { setApiKey((d.keys || []).find((k) => k.isActive !== false)?.key || ""); }) .catch(() => {}); - fetch("/api/tunnel/status") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/status") .then((r) => r.json()) .then((d) => { if (d.publicUrl) setTunnelEndpoint(d.publicUrl); }) .catch(() => {}); // Load active connections of this provider for pinning - fetch("/api/providers/client") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/client") .then((r) => r.json()) .then((d) => { const conns = (d.connections || []).filter((c) => c.provider === providerId && c.isActive !== false); @@ -1454,16 +1454,16 @@ function SttExampleCard({ providerId }) { useEffect(() => { setLocalEndpoint(window.location.origin); - fetch("/api/keys") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/keys") .then((r) => r.json()) .then((d) => { setApiKey((d.keys || []).find((k) => k.isActive !== false)?.key || ""); }) .catch(() => {}); - fetch("/api/tunnel/status") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/tunnel/status") .then((r) => r.json()) .then((d) => { if (d.publicUrl) setTunnelEndpoint(d.publicUrl); }) .catch(() => {}); const loadCustom = () => { - fetch("/api/models/custom", { cache: "no-store" }) + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/custom", { cache: "no-store" }) .then((r) => r.json()) .then((d) => { const list = (d.models || []).filter((m) => m.type === "stt" && m.providerAlias === providerAlias); @@ -1505,7 +1505,7 @@ function SttExampleCard({ providerId }) { const headers = {}; if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`; - const res = await fetch("/api/v1/audio/transcriptions", { method: "POST", headers, body: fd }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/v1/audio/transcriptions", { method: "POST", headers, body: fd }); setLatency(Date.now() - start); const ct = res.headers.get("content-type") || ""; const data = ct.includes("application/json") ? await res.json() : await res.text(); @@ -1732,7 +1732,7 @@ export default function MediaProviderDetailPage() { useEffect(() => { if (!isCustom) return; let cancelled = false; - fetch("/api/provider-nodes", { cache: "no-store" }) + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes", { cache: "no-store" }) .then((r) => r.json()) .then((d) => { if (cancelled) return; diff --git a/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js b/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js index e7d0cf28a25..e30fcf7b609 100644 --- a/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js +++ b/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js @@ -158,18 +158,18 @@ export default function MediaProviderKindPage() { useEffect(() => { if (!kindConfig) return; - fetch("/api/providers", { cache: "no-store" }) + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { cache: "no-store" }) .then((r) => r.json()) .then((d) => setConnections(d.connections || [])) .catch(() => {}); if (isEmbedding) { - fetch("/api/provider-nodes", { cache: "no-store" }) + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes", { cache: "no-store" }) .then((r) => r.json()) .then((d) => setCustomNodes((d.nodes || []).filter((n) => n.type === "custom-embedding"))) .catch(() => {}); } if (supportsCombo) { - fetch("/api/combos", { cache: "no-store" }) + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/combos", { cache: "no-store" }) .then((r) => r.json()) .then((d) => setCombos(d.combos || [])) .catch(() => {}); @@ -213,7 +213,7 @@ export default function MediaProviderKindPage() { let i = 1; const existing = new Set(combos.map((c) => c.name)); while (existing.has(name)) { name = `${base}-${i++}`; } - const res = await fetch("/api/combos", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/combos", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, models: [], kind }), diff --git a/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js b/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js index 45c1546025d..56f3b41b1c9 100644 --- a/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js +++ b/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js @@ -66,11 +66,11 @@ export default function ComboDetailPage() { try { const [comboRes, settingsRes, logsRes, keysRes, connsRes, aliasesRes] = await Promise.all([ fetch(`/api/combos/${id}`, { cache: "no-store" }), - fetch("/api/settings", { cache: "no-store" }), - fetch("/api/usage/logs", { cache: "no-store" }), - fetch("/api/keys", { cache: "no-store" }), - fetch("/api/providers", { cache: "no-store" }), - fetch("/api/models/alias", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/usage/logs", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/keys", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias", { cache: "no-store" }), ]); if (aliasesRes.ok) setModelAliases((await aliasesRes.json()).aliases || {}); if (keysRes.ok) { @@ -151,12 +151,12 @@ export default function ComboDetailPage() { const handleToggleRoundRobin = async (enabled) => { setRoundRobin(enabled); - const settingsRes = await fetch("/api/settings", { cache: "no-store" }); + const settingsRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { cache: "no-store" }); const s = settingsRes.ok ? await settingsRes.json() : {}; const updated = { ...(s.comboStrategies || {}) }; if (enabled) updated[combo.name] = { fallbackStrategy: "round-robin" }; else delete updated[combo.name]; - await fetch("/api/settings", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ comboStrategies: updated }), diff --git a/src/app/(dashboard)/dashboard/media-providers/web/page.js b/src/app/(dashboard)/dashboard/media-providers/web/page.js index 755126da505..d51dc0a1972 100644 --- a/src/app/(dashboard)/dashboard/media-providers/web/page.js +++ b/src/app/(dashboard)/dashboard/media-providers/web/page.js @@ -151,8 +151,8 @@ export default function WebProvidersPage() { const fetchAll = async () => { try { const [connsRes, combosRes] = await Promise.all([ - fetch("/api/providers", { cache: "no-store" }), - fetch("/api/combos", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/combos", { cache: "no-store" }), ]); if (connsRes.ok) setConnections((await connsRes.json()).connections || []); if (combosRes.ok) setCombos((await combosRes.json()).combos || []); @@ -174,7 +174,7 @@ export default function WebProvidersPage() { let i = 1; const existing = new Set(combos.map((c) => c.name)); while (existing.has(name)) { name = `${base}-${i++}`; } - const res = await fetch("/api/combos", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/combos", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, models: [], kind }), diff --git a/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js b/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js index 2b0169b3564..c49ee796e4a 100644 --- a/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js +++ b/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js @@ -23,7 +23,7 @@ export default function MitmPageClient() { const fetchConnections = async () => { try { - const res = await fetch("/api/providers"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers"); if (res.ok) { const data = await res.json(); setConnections(data.connections || []); @@ -33,7 +33,7 @@ export default function MitmPageClient() { const fetchApiKeys = async () => { try { - const res = await fetch("/api/keys"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/keys"); if (res.ok) { const data = await res.json(); setApiKeys(data.keys || []); @@ -43,7 +43,7 @@ export default function MitmPageClient() { const fetchAliases = async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); if (res.ok) { const data = await res.json(); setModelAliases(data.aliases || {}); @@ -53,7 +53,7 @@ export default function MitmPageClient() { const fetchCloudSettings = async () => { try { - const res = await fetch("/api/settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings"); if (res.ok) { const data = await res.json(); setCloudEnabled(data.cloudEnabled || false); diff --git a/src/app/(dashboard)/dashboard/profile/page.js b/src/app/(dashboard)/dashboard/profile/page.js index e5e40642409..41b00a35cff 100644 --- a/src/app/(dashboard)/dashboard/profile/page.js +++ b/src/app/(dashboard)/dashboard/profile/page.js @@ -63,7 +63,7 @@ export default function ProfilePage() { }, [langOpen]); useEffect(() => { - fetch("/api/settings") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings") .then((res) => res.json()) .then((data) => { setSettings(data); @@ -102,7 +102,7 @@ export default function ProfilePage() { setProxyStatus({ type: "", message: "" }); try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -138,7 +138,7 @@ export default function ProfilePage() { setProxyStatus({ type: "", message: "" }); try { - const res = await fetch("/api/settings/proxy-test", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings/proxy-test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ proxyUrl }), @@ -168,7 +168,7 @@ export default function ProfilePage() { setProxyStatus({ type: "", message: "" }); try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ outboundProxyEnabled }), @@ -203,7 +203,7 @@ export default function ProfilePage() { setPassStatus({ type: "", message: "" }); try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -229,7 +229,7 @@ export default function ProfilePage() { const updateFallbackStrategy = async (strategy) => { try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ fallbackStrategy: strategy }), @@ -244,7 +244,7 @@ export default function ProfilePage() { const updateComboStrategy = async (strategy) => { try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ comboStrategy: strategy }), @@ -262,7 +262,7 @@ export default function ProfilePage() { if (isNaN(numLimit) || numLimit < 1) return; try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ stickyRoundRobinLimit: numLimit }), @@ -280,7 +280,7 @@ export default function ProfilePage() { if (isNaN(numLimit) || numLimit < 1) return; try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ comboStickyRoundRobinLimit: numLimit }), @@ -295,7 +295,7 @@ export default function ProfilePage() { const updateRequireLogin = async (requireLogin) => { try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ requireLogin }), @@ -340,7 +340,7 @@ export default function ProfilePage() { payload.oidcClientSecret = secret; } - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), @@ -392,7 +392,7 @@ export default function ProfilePage() { setOidcTestStatus({ type: "", message: "" }); try { - const saveRes = await fetch("/api/settings", { + const saveRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -414,7 +414,7 @@ export default function ProfilePage() { return; } - const res = await fetch("/api/auth/oidc/test", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/auth/oidc/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -447,7 +447,7 @@ export default function ProfilePage() { const updateObservabilityEnabled = async (enabled) => { try { - const res = await fetch("/api/settings", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ enableObservability: enabled }), @@ -462,7 +462,7 @@ export default function ProfilePage() { const reloadSettings = async () => { try { - const res = await fetch("/api/settings"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings"); if (!res.ok) return; const data = await res.json(); setSettings(data); @@ -475,7 +475,7 @@ export default function ProfilePage() { setDbLoading(true); setDbStatus({ type: "", message: "" }); try { - const res = await fetch("/api/settings/database"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings/database"); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || "Failed to export database"); @@ -513,7 +513,7 @@ export default function ProfilePage() { const raw = await file.text(); const payload = JSON.parse(raw); - const res = await fetch("/api/settings/database", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings/database", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), @@ -541,7 +541,7 @@ export default function ProfilePage() { const handleShutdown = async () => { setIsShuttingDown(true); try { - await fetch("/api/version/shutdown", { method: "POST" }); + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/version/shutdown", { method: "POST" }); } catch (e) { // Expected to fail as server shuts down; ignore error } @@ -551,9 +551,10 @@ export default function ProfilePage() { const handleLogout = async () => { try { - const res = await fetch("/api/auth/logout", { method: "POST" }); + const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""; + const res = await fetch(basePath + "/api/auth/logout", { method: "POST" }); if (res.ok) { - router.push("/login"); + router.push(basePath + "/login"); router.refresh(); } } catch (err) { diff --git a/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js b/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js index 77fd9c07bcf..ebf5755316b 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js @@ -69,7 +69,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa const handleValidate = async () => { setValidating(true); try { - const res = await fetch("/api/providers/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider, apiKey: formData.apiKey, providerSpecificData: buildProviderSpecificData() }), @@ -98,7 +98,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa try { setValidating(true); setValidationResult(null); - const res = await fetch("/api/providers/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider, apiKey: formData.apiKey, providerSpecificData: buildProviderSpecificData() }), @@ -139,7 +139,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa const baseName = parts.length >= 2 ? parts[0].trim() : "Key"; const name = `${baseName} ${i + 1}`; try { - const res = await fetch("/api/providers", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider, apiKey, name, priority: 1, testStatus: "unknown" }), diff --git a/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js b/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js index d0bf1659dd8..0b8c5c3b267 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js @@ -27,7 +27,7 @@ export default function AddCustomModelModal({ isOpen, providerAlias, providerDis setTestStatus("testing"); setTestError(""); try { - const res = await fetch("/api/models/test", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: `${providerAlias}/${cleanId}` }), diff --git a/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js b/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js index 33f1f05aa26..74a04ffb696 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js @@ -81,7 +81,7 @@ export default function CompatibleModelsSection({ providerStorageAlias, provider if (testingModelId) return; setTestingModelId(modelId); try { - const res = await fetch("/api/models/test", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: `${providerStorageAlias}/${modelId}` }), diff --git a/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js b/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js index d6d33de1609..18a77076b53 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js @@ -54,7 +54,7 @@ export default function EditCompatibleNodeModal({ isOpen, node, onSave, onClose, const handleValidate = async () => { setValidating(true); try { - const res = await fetch("/api/provider-nodes/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.js b/src/app/(dashboard)/dashboard/providers/[id]/page.js index e49827b1118..feb79520d55 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/page.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/page.js @@ -156,7 +156,7 @@ export default function ProviderDetailPage() { const handleDisableModel = async (modelId) => { try { - const res = await fetch("/api/models/disabled", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/disabled", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerAlias: providerStorageAlias, ids: [modelId] }), @@ -184,7 +184,7 @@ export default function ProviderDetailPage() { onConfirm: async () => { setConfirmState(null); try { - const res = await fetch("/api/models/disabled", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/disabled", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerAlias: providerStorageAlias, ids }), @@ -209,7 +209,7 @@ export default function ProviderDetailPage() { // Define callbacks BEFORE the useEffect that uses them const fetchAliases = useCallback(async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) { setModelAliases(data.aliases || {}); @@ -222,7 +222,7 @@ export default function ProviderDetailPage() { // Fetch free models from Kilo API for kilocode provider useEffect(() => { if (providerId !== "kilocode") return; - fetch("/api/providers/kilo/free-models") + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/kilo/free-models") .then((res) => res.json()) .then((data) => { if (data.models?.length) setKiloFreeModels(data.models); }) .catch(() => {}); @@ -231,10 +231,10 @@ export default function ProviderDetailPage() { const fetchConnections = useCallback(async () => { try { const [connectionsRes, nodesRes, proxyPoolsRes, settingsRes] = await Promise.all([ - fetch("/api/providers", { cache: "no-store" }), - fetch("/api/provider-nodes", { cache: "no-store" }), - fetch("/api/proxy-pools?isActive=true", { cache: "no-store" }), - fetch("/api/settings", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/proxy-pools?isActive=true", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { cache: "no-store" }), ]); const connectionsData = await connectionsRes.json(); const nodesData = await nodesRes.json(); @@ -262,7 +262,7 @@ export default function ProviderDetailPage() { if (!node && isCompatible) { for (let attempt = 0; attempt < 3; attempt += 1) { await new Promise((resolve) => setTimeout(resolve, 150)); - const retryRes = await fetch("/api/provider-nodes", { cache: "no-store" }); + const retryRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes", { cache: "no-store" }); if (!retryRes.ok) continue; const retryData = await retryRes.json(); node = (retryData.nodes || []).find((entry) => entry.id === providerId) || null; @@ -299,7 +299,7 @@ export default function ProviderDetailPage() { const saveProviderStrategy = async (strategy, stickyLimit) => { try { - const settingsRes = await fetch("/api/settings", { cache: "no-store" }); + const settingsRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { cache: "no-store" }); const settingsData = settingsRes.ok ? await settingsRes.json() : {}; const current = settingsData.providerStrategies || {}; @@ -317,7 +317,7 @@ export default function ProviderDetailPage() { updated[providerId] = override; } - await fetch("/api/settings", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerStrategies: updated }), @@ -342,7 +342,7 @@ export default function ProviderDetailPage() { const saveThinkingConfig = async (mode) => { try { - const settingsRes = await fetch("/api/settings", { cache: "no-store" }); + const settingsRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { cache: "no-store" }); const settingsData = settingsRes.ok ? await settingsRes.json() : {}; const current = settingsData.providerThinking || {}; const updated = { ...current }; @@ -351,7 +351,7 @@ export default function ProviderDetailPage() { } else { updated[providerId] = { mode }; } - await fetch("/api/settings", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerThinking: updated }), @@ -382,7 +382,7 @@ export default function ProviderDetailPage() { const handleSetAlias = async (modelId, alias, providerAliasOverride = providerAlias) => { const fullModel = `${providerAliasOverride}/${modelId}`; try { - const res = await fetch("/api/models/alias", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: fullModel, alias }), @@ -594,7 +594,7 @@ export default function ProviderDetailPage() { const handleSaveApiKey = async (formData) => { setAddConnectionError(""); try { - const res = await fetch("/api/providers", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: providerId, ...formData }), @@ -875,7 +875,7 @@ export default function ProviderDetailPage() { if (testingModelId) return; setTestingModelId(modelId); try { - const res = await fetch("/api/models/test", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: `${providerStorageAlias}/${modelId}` }), diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.new.js b/src/app/(dashboard)/dashboard/providers/[id]/page.new.js index 16f4a36893b..72dc182877a 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/page.new.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/page.new.js @@ -75,7 +75,7 @@ export default function ProviderDetailPage() { // Define callbacks BEFORE the useEffect that uses them const fetchAliases = useCallback(async () => { try { - const res = await fetch("/api/models/alias"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"); const data = await res.json(); if (res.ok) { setModelAliases(data.aliases || {}); @@ -88,8 +88,8 @@ export default function ProviderDetailPage() { const fetchConnections = useCallback(async () => { try { const [connectionsRes, nodesRes] = await Promise.all([ - fetch("/api/providers", { cache: "no-store" }), - fetch("/api/provider-nodes", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes", { cache: "no-store" }), ]); const connectionsData = await connectionsRes.json(); const nodesData = await nodesRes.json(); @@ -105,7 +105,7 @@ export default function ProviderDetailPage() { if (!node && isCompatible) { for (let attempt = 0; attempt < 3; attempt += 1) { await new Promise((resolve) => setTimeout(resolve, 150)); - const retryRes = await fetch("/api/provider-nodes", { cache: "no-store" }); + const retryRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes", { cache: "no-store" }); if (!retryRes.ok) continue; const retryData = await retryRes.json(); node = (retryData.nodes || []).find((entry) => entry.id === providerId) || null; @@ -252,7 +252,7 @@ export default function ProviderDetailPage() { const handleSetAlias = async (modelId, alias, providerAliasOverride = providerAlias) => { const fullModel = `${providerAliasOverride}/${modelId}`; try { - const res = await fetch("/api/models/alias", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: fullModel, alias }), @@ -300,7 +300,7 @@ export default function ProviderDetailPage() { const handleSaveApiKey = async (formData) => { try { - const res = await fetch("/api/providers", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: providerId, ...formData }), @@ -1277,7 +1277,7 @@ function AddApiKeyModal({ isOpen, provider, providerName, isCompatible, isAnthro const handleValidate = async () => { setValidating(true); try { - const res = await fetch("/api/providers/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider, apiKey: formData.apiKey }), @@ -1300,7 +1300,7 @@ function AddApiKeyModal({ isOpen, provider, providerName, isCompatible, isAnthro try { setValidating(true); setValidationResult(null); - const res = await fetch("/api/providers/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider, apiKey: formData.apiKey }), @@ -1436,7 +1436,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }) { setValidating(true); setValidationResult(null); try { - const res = await fetch("/api/providers/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: connection.provider, apiKey: formData.apiKey }), @@ -1461,7 +1461,7 @@ function EditConnectionModal({ isOpen, connection, onSave, onClose }) { try { setValidating(true); setValidationResult(null); - const res = await fetch("/api/providers/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: connection.provider, apiKey: formData.apiKey }), @@ -1625,7 +1625,7 @@ function EditCompatibleNodeModal({ isOpen, node, onSave, onClose, isAnthropic }) const handleValidate = async () => { setValidating(true); try { - const res = await fetch("/api/provider-nodes/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js b/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js index b8d196ce51d..b9cbf1b112f 100644 --- a/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js +++ b/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js @@ -208,7 +208,7 @@ function AddApiKeyModal({ isOpen, provider, providerName, proxyPools, onSave, on const handleValidate = async () => { setValidating(true); try { - const res = await fetch("/api/providers/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider, apiKey: formData.apiKey }), @@ -226,7 +226,7 @@ function AddApiKeyModal({ isOpen, provider, providerName, proxyPools, onSave, on let isValid = false; try { setValidating(true); setValidationResult(null); - const res = await fetch("/api/providers/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider, apiKey: formData.apiKey }), @@ -313,9 +313,9 @@ export default function ConnectionsCard({ providerId, isOAuth }) { const fetch_ = useCallback(async () => { try { const [connRes, proxyRes, settingsRes] = await Promise.all([ - fetch("/api/providers", { cache: "no-store" }), - fetch("/api/proxy-pools?isActive=true", { cache: "no-store" }), - fetch("/api/settings", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/proxy-pools?isActive=true", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { cache: "no-store" }), ]); const connData = await connRes.json(); const proxyData = await proxyRes.json(); @@ -333,7 +333,7 @@ export default function ConnectionsCard({ providerId, isOAuth }) { const saveStrategy = async (strategy, stickyLimit) => { try { - const res = await fetch("/api/settings", { cache: "no-store" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { cache: "no-store" }); const data = res.ok ? await res.json() : {}; const current = data.providerStrategies || {}; const override = {}; @@ -342,7 +342,7 @@ export default function ConnectionsCard({ providerId, isOAuth }) { const updated = { ...current }; if (Object.keys(override).length === 0) delete updated[providerId]; else updated[providerId] = override; - await fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerStrategies: updated }) }); + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerStrategies: updated }) }); } catch (e) { console.log("saveStrategy error:", e); } }; @@ -388,7 +388,7 @@ export default function ConnectionsCard({ providerId, isOAuth }) { const handleSaveApiKey = async (formData) => { try { - const res = await fetch("/api/providers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: providerId, ...formData }) }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider: providerId, ...formData }) }); if (res.ok) { await fetch_(); setShowAddModal(false); } } catch (e) { console.log("save apikey error:", e); } }; diff --git a/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js b/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js index 9150bef16d5..ed66703c6ca 100644 --- a/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js +++ b/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js @@ -28,7 +28,7 @@ export default function ModelAvailabilityBadge() { const fetchStatus = useCallback(async () => { try { - const res = await fetch("/api/models/availability"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/availability"); if (res.ok) { const json = await res.json(); setData(json); @@ -58,7 +58,7 @@ export default function ModelAvailabilityBadge() { const handleClearCooldown = async (provider, model) => { setClearing(`${provider}:${model}`); try { - const res = await fetch("/api/models/availability", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/availability", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "clearCooldown", provider, model }), diff --git a/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js b/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js index f55d001fc1c..0578ef57ce3 100644 --- a/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js +++ b/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js @@ -124,9 +124,9 @@ export default function ModelsCard({ providerId, kindFilter, providerAliasOverri const fetchData = useCallback(async () => { try { const [aliasRes, connRes, customRes] = await Promise.all([ - fetch("/api/models/alias"), - fetch("/api/providers", { cache: "no-store" }), - fetch("/api/models/custom", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { cache: "no-store" }), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/custom", { cache: "no-store" }), ]); const aliasData = await aliasRes.json(); const connData = await connRes.json(); @@ -142,7 +142,7 @@ export default function ModelsCard({ providerId, kindFilter, providerAliasOverri const handleSetAlias = async (modelId, alias) => { const fullModel = `${providerAlias}/${modelId}`; try { - const res = await fetch("/api/models/alias", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/alias", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: fullModel, alias }), @@ -160,7 +160,7 @@ export default function ModelsCard({ providerId, kindFilter, providerAliasOverri const handleAddCustomModel = async (modelId) => { try { - const res = await fetch("/api/models/custom", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/custom", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ providerAlias, id: modelId, type: effectiveType }), @@ -187,7 +187,7 @@ export default function ModelsCard({ providerId, kindFilter, providerAliasOverri if (testingModelId) return; setTestingModelId(modelId); try { - const res = await fetch("/api/models/test", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/models/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: `${providerAlias}/${modelId}`, kind: kindFilter }), diff --git a/src/app/(dashboard)/dashboard/providers/new/page.js b/src/app/(dashboard)/dashboard/providers/new/page.js index 57d0a7f5903..99bb3a656e4 100644 --- a/src/app/(dashboard)/dashboard/providers/new/page.js +++ b/src/app/(dashboard)/dashboard/providers/new/page.js @@ -51,7 +51,7 @@ export default function NewProviderPage() { setLoading(true); try { - const response = await fetch("/api/providers", { + const response = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData), diff --git a/src/app/(dashboard)/dashboard/providers/page.js b/src/app/(dashboard)/dashboard/providers/page.js index 8ca94ab8c84..94e54b29923 100644 --- a/src/app/(dashboard)/dashboard/providers/page.js +++ b/src/app/(dashboard)/dashboard/providers/page.js @@ -144,8 +144,8 @@ export default function ProvidersPage() { const fetchData = async () => { try { const [connectionsRes, nodesRes] = await Promise.all([ - fetch("/api/providers"), - fetch("/api/provider-nodes"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers"), + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes"), ]); const connectionsData = await connectionsRes.json(); const nodesData = await nodesRes.json(); @@ -232,7 +232,7 @@ export default function ProvidersPage() { setTestingMode(mode === "provider" ? providerId : mode); setTestResults(null); try { - const res = await fetch("/api/providers/test-batch", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/providers/test-batch", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode, providerId }), @@ -873,7 +873,7 @@ function AddOpenAICompatibleModal({ isOpen, onClose, onCreated }) { return; setSubmitting(true); try { - const res = await fetch("/api/provider-nodes", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -906,7 +906,7 @@ function AddOpenAICompatibleModal({ isOpen, onClose, onCreated }) { const handleValidate = async () => { setValidating(true); try { - const res = await fetch("/api/provider-nodes/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -1065,7 +1065,7 @@ function AddAnthropicCompatibleModal({ isOpen, onClose, onCreated }) { return; setSubmitting(true); try { - const res = await fetch("/api/provider-nodes", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ @@ -1096,7 +1096,7 @@ function AddAnthropicCompatibleModal({ isOpen, onClose, onCreated }) { const handleValidate = async () => { setValidating(true); try { - const res = await fetch("/api/provider-nodes/validate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/src/app/(dashboard)/dashboard/proxy-pools/page.js b/src/app/(dashboard)/dashboard/proxy-pools/page.js index 8edf791f5a6..de246c7a6e8 100644 --- a/src/app/(dashboard)/dashboard/proxy-pools/page.js +++ b/src/app/(dashboard)/dashboard/proxy-pools/page.js @@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useState, useRef } from "react"; import { Badge, Button, Card, CardSkeleton, Input, Modal, Toggle, ConfirmModal } from "@/shared/components"; import { useNotificationStore } from "@/store/notificationStore"; +import { withBasePath } from "@/shared/utils/basePath"; function getStatusVariant(status) { if (status === "active") return "success"; @@ -68,7 +69,7 @@ export default function ProxyPoolsPage() { const fetchProxyPools = useCallback(async () => { try { - const res = await fetch("/api/proxy-pools?includeUsage=true", { cache: "no-store" }); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/proxy-pools?includeUsage=true", { cache: "no-store" }); const data = await res.json(); if (res.ok) { setProxyPools(data.proxyPools || []); @@ -119,7 +120,7 @@ export default function ProxyPoolsPage() { setSaving(true); try { const isEdit = !!editingProxyPool; - const res = await fetch(isEdit ? `/api/proxy-pools/${editingProxyPool.id}` : "/api/proxy-pools", { + const res = await fetch(withBasePath(isEdit ? `/api/proxy-pools/${editingProxyPool.id}` : `/api/proxy-pools`), { method: isEdit ? "PUT" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), @@ -376,7 +377,7 @@ export default function ProxyPoolsPage() { if (!vercelForm.vercelToken.trim()) return; setDeploying(true); try { - const res = await fetch("/api/proxy-pools/vercel-deploy", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/proxy-pools/vercel-deploy", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(vercelForm), @@ -401,7 +402,7 @@ export default function ProxyPoolsPage() { if (!cloudflareForm.accountId.trim() || !cloudflareForm.apiToken.trim()) return; setDeploying(true); try { - const res = await fetch("/api/proxy-pools/cloudflare-deploy", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/proxy-pools/cloudflare-deploy", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(cloudflareForm), @@ -426,7 +427,7 @@ export default function ProxyPoolsPage() { if (!denoForm.denoToken.trim()) return; setDeploying(true); try { - const res = await fetch("/api/proxy-pools/deno-deploy", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/proxy-pools/deno-deploy", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(denoForm), @@ -528,7 +529,7 @@ export default function ProxyPoolsPage() { continue; } - const res = await fetch("/api/proxy-pools", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/proxy-pools", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/src/app/(dashboard)/dashboard/translator/page.js b/src/app/(dashboard)/dashboard/translator/page.js index df6b7438084..592e9c970f5 100644 --- a/src/app/(dashboard)/dashboard/translator/page.js +++ b/src/app/(dashboard)/dashboard/translator/page.js @@ -68,7 +68,7 @@ export default function TranslatorPage() { const detectMeta = async (rawContent) => { try { const body = typeof rawContent === "string" ? JSON.parse(rawContent) : rawContent; - const res = await fetch("/api/translator/translate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/translator/translate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ step: 1, body }) @@ -78,7 +78,7 @@ export default function TranslatorPage() { } catch { /* ignore */ } }; - const save = (file, content) => fetch("/api/translator/save", { + const save = (file, content) => fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/translator/save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ file, content }) @@ -94,7 +94,7 @@ export default function TranslatorPage() { save("1_req_client.json", raw); save("2_req_source.json", JSON.stringify({ timestamp: new Date().toISOString(), headers: {}, body: body.body || body }, null, 2)); - const res = await fetch("/api/translator/translate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/translator/translate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ step: 2, body }) @@ -117,7 +117,7 @@ export default function TranslatorPage() { // Save input: 3_req_openai.json save("3_req_openai.json", raw); - const res = await fetch("/api/translator/translate", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/translator/translate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ step: 3, body: { ...openaiBody, provider: meta?.provider, model: meta?.model } }) @@ -150,7 +150,7 @@ export default function TranslatorPage() { return; } - const res = await fetch("/api/translator/send", { + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/translator/send", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ provider, model, body: step4.body || step4 }) @@ -176,7 +176,7 @@ export default function TranslatorPage() { openNext(5); // Save to logs/translator/5_res_provider.txt - await fetch("/api/translator/save", { + await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/translator/save", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ file: "5_res_provider.txt", content: full }) diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js index 9498ed309e3..6106b1e8f86 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js @@ -492,7 +492,7 @@ export default function ProviderLimits() { useEffect(() => { let cancelled = false; - fetch("/api/proxy-pools?isActive=true", { cache: "no-store" }) + fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/proxy-pools?isActive=true", { cache: "no-store" }) .then((res) => res.json()) .then((data) => { if (!cancelled && data?.proxyPools) { diff --git a/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js b/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js index e450dd941ec..18743182381 100644 --- a/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js +++ b/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js @@ -16,7 +16,7 @@ async function fetchProviderNames() { return { providerNameCache, providerNodesCache }; } - const nodesRes = await fetch("/api/provider-nodes"); + const nodesRes = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/provider-nodes"); const nodesData = await nodesRes.json(); const nodes = nodesData.nodes || []; providerNodesCache = {}; @@ -109,7 +109,7 @@ export default function RequestDetailsTab() { const fetchProviders = useCallback(async () => { try { - const res = await fetch("/api/usage/providers"); + const res = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/usage/providers"); const data = await res.json(); setProviders(data.providers || []); diff --git a/src/app/api/auth/oidc/callback/route.js b/src/app/api/auth/oidc/callback/route.js index 2e6a253840b..d369a0106cb 100644 --- a/src/app/api/auth/oidc/callback/route.js +++ b/src/app/api/auth/oidc/callback/route.js @@ -11,6 +11,11 @@ import { } from "@/lib/auth/oidc"; import { setDashboardAuthCookie } from "@/lib/auth/dashboardSession"; +function basePathUrl(path, request) { + const bp = request.nextUrl?.basePath || process.env.NINEROUTER_BASE_PATH || process.env.NEXT_PUBLIC_BASE_PATH || ""; + return new URL(bp + path, getPublicOrigin(request)); +} + function clearOidcCookies(cookieStore) { cookieStore.delete("oidc_state"); cookieStore.delete("oidc_nonce"); @@ -21,13 +26,13 @@ export async function GET(request) { const url = new URL(request.url); const error = url.searchParams.get("error"); if (error) { - return NextResponse.redirect(new URL(`/login?error=${encodeURIComponent(error)}`, getPublicOrigin(request))); + return NextResponse.redirect(basePathUrl(`/login?error=${encodeURIComponent(error)}`, request)); } const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); if (!code || !state) { - return NextResponse.redirect(new URL("/login?error=oidc_missing_code", getPublicOrigin(request))); + return NextResponse.redirect(basePathUrl("/login?error=oidc_missing_code", request)); } const cookieStore = await cookies(); @@ -37,19 +42,19 @@ export async function GET(request) { if (!storedState || !storedNonce || !codeVerifier || storedState !== state) { clearOidcCookies(cookieStore); - return NextResponse.redirect(new URL("/login?error=oidc_invalid_state", getPublicOrigin(request))); + return NextResponse.redirect(basePathUrl("/login?error=oidc_invalid_state", request)); } try { const config = await getOidcRuntimeConfig(); if (!config) { clearOidcCookies(cookieStore); - return NextResponse.redirect(new URL("/login?error=oidc_not_configured", getPublicOrigin(request))); + return NextResponse.redirect(basePathUrl("/login?error=oidc_not_configured", request)); } const discovery = await fetchOidcDiscovery(config.issuerUrl); const discoveredIssuer = discovery.issuer || config.issuerUrl; - const redirectUri = `${getPublicOrigin(request)}/api/auth/oidc/callback`; + const redirectUri = basePathUrl("/api/auth/oidc/callback", request).href; const tokenData = await exchangeOidcCode({ tokenEndpoint: discovery.token_endpoint, clientId: config.clientId, @@ -79,9 +84,9 @@ export async function GET(request) { oidcName: pickOidcDisplayName(payload), }); - return NextResponse.redirect(new URL("/dashboard", getPublicOrigin(request))); + return NextResponse.redirect(basePathUrl("/dashboard", request)); } catch (error) { clearOidcCookies(cookieStore); - return NextResponse.redirect(new URL(`/login?error=${encodeURIComponent(error.message || "oidc_callback_failed")}`, getPublicOrigin(request))); + return NextResponse.redirect(basePathUrl(`/login?error=${encodeURIComponent(error.message || "oidc_callback_failed")}`, request)); } } diff --git a/src/app/api/auth/oidc/start/route.js b/src/app/api/auth/oidc/start/route.js index d4ae5e52f26..88d27221b6b 100644 --- a/src/app/api/auth/oidc/start/route.js +++ b/src/app/api/auth/oidc/start/route.js @@ -11,18 +11,23 @@ import { } from "@/lib/auth/oidc"; import { shouldUseSecureCookie } from "@/lib/auth/dashboardSession"; +function basePathUrl(path, request) { + const bp = request.nextUrl?.basePath || process.env.NINEROUTER_BASE_PATH || process.env.NEXT_PUBLIC_BASE_PATH || ""; + return new URL(bp + path, getPublicOrigin(request)); +} + export async function GET(request) { try { const config = await getOidcRuntimeConfig(); if (!config) { - return NextResponse.redirect(new URL("/login?error=oidc_not_configured", getPublicOrigin(request))); + return NextResponse.redirect(basePathUrl("/login?error=oidc_not_configured", request)); } const discovery = await fetchOidcDiscovery(config.issuerUrl); const state = createOidcState(); const nonce = createOidcNonce(); const { verifier, challenge } = createPkcePair(); - const redirectUri = `${getPublicOrigin(request)}/api/auth/oidc/callback`; + const redirectUri = basePathUrl("/api/auth/oidc/callback", request).href; const authUrl = buildOidcAuthorizationUrl({ authorizationEndpoint: discovery.authorization_endpoint, clientId: config.clientId, @@ -47,6 +52,6 @@ export async function GET(request) { return NextResponse.redirect(authUrl); } catch (error) { - return NextResponse.redirect(new URL(`/login?error=${encodeURIComponent(error.message || "oidc_start_failed")}`, getPublicOrigin(request))); + return NextResponse.redirect(basePathUrl(`/login?error=${encodeURIComponent(error.message || "oidc_start_failed")}`, request)); } } diff --git a/src/app/api/auth/oidc/test/route.js b/src/app/api/auth/oidc/test/route.js index 85f0aaf6ba8..d0ba8162e85 100644 --- a/src/app/api/auth/oidc/test/route.js +++ b/src/app/api/auth/oidc/test/route.js @@ -13,6 +13,11 @@ async function canAccessTestRoute() { return await verifyDashboardAuthToken(token); } +function basePathUrl(path, request) { + const bp = request.nextUrl?.basePath || process.env.NINEROUTER_BASE_PATH || process.env.NEXT_PUBLIC_BASE_PATH || ""; + return new URL(bp + path, getPublicOrigin(request)); +} + export async function POST(request) { try { if (!(await canAccessTestRoute())) { @@ -39,7 +44,7 @@ export async function POST(request) { } const discovery = await fetchOidcDiscovery(issuerUrl); - const redirectUri = `${getPublicOrigin(request)}/api/auth/oidc/callback`; + const redirectUri = basePathUrl("/api/auth/oidc/callback", request).href; const secretProbe = await probeOidcClientSecret({ tokenEndpoint: discovery.token_endpoint, clientId, diff --git a/src/app/dashboard/settings/pricing/page.js b/src/app/dashboard/settings/pricing/page.js index 0c98c5164f2..3acee92f5f2 100644 --- a/src/app/dashboard/settings/pricing/page.js +++ b/src/app/dashboard/settings/pricing/page.js @@ -18,7 +18,7 @@ export default function PricingSettingsPage() { const loadPricing = async () => { setLoading(true); try { - const response = await fetch("/api/pricing"); + const response = await fetch((process.env.NEXT_PUBLIC_BASE_PATH || "") + "/api/pricing"); if (response.ok) { const data = await response.json(); setCurrentPricing(data); diff --git a/src/app/landing/components/Footer.js b/src/app/landing/components/Footer.js index 91209a5af7e..3df329b51b9 100644 --- a/src/app/landing/components/Footer.js +++ b/src/app/landing/components/Footer.js @@ -1,5 +1,7 @@ "use client"; +const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || ""; + export default function Footer() { return (