"use client"; import { useState, useEffect, useCallback } from "react"; import Link from "next/link"; import { useTranslations } from "next-intl"; import { Card, Button, Input, Toggle } from "@/shared/components"; interface Settings { cliproxyapi_fallback_enabled?: boolean; cliproxyapi_url?: string; cliproxyapi_fallback_codes?: string; [key: string]: unknown; } interface VersionManagerEntry { tool: string; status: string; installedVersion: string | null; healthStatus: string; port: number; } function isValidUrl(value: string): boolean { try { const url = new URL(value); return url.protocol === "http:" || url.protocol === "https:"; } catch { return false; } } export default function CliproxyapiSettingsTab() { const t = useTranslations("settings"); const [settings, setSettings] = useState({}); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [message, setMessage] = useState<{ type: string; text: string } | null>(null); const [toolState, setToolState] = useState(null); const [toolStateError, setToolStateError] = useState(null); // #1934: import CLIProxyAPI auth files (~/.cli-proxy-api/) as OmniRoute connections. const [importing, setImporting] = useState(false); const [importResult, setImportResult] = useState(null); const handleImportAuth = useCallback(async () => { setImporting(true); setImportResult(null); try { const res = await fetch("/api/oauth/cliproxy-import", { method: "POST" }); const data = await res.json(); if (res.ok) { setImportResult( `Imported ${data.imported ?? 0} account(s) (scanned ${data.scanned ?? 0}, skipped ${data.skipped ?? 0}).` ); } else { setImportResult(data.error || "Import failed."); } } catch { setImportResult("Import failed."); } finally { setImporting(false); } }, []); useEffect(() => { fetch("/api/settings") .then((r) => { if (!r.ok) throw new Error(`Settings API returned ${r.status}`); return r.json(); }) .then((data) => { setSettings(data); setLoading(false); }) .catch((err) => { console.error("Failed to load settings:", err); setLoading(false); }); fetch("/api/version-manager/status") .then((r) => { if (!r.ok) throw new Error(`Version manager API returned ${r.status}`); return r.json(); }) .then((data) => { const entry = Array.isArray(data) ? data.find((t: VersionManagerEntry) => t.tool === "cliproxyapi") : null; setToolState(entry ?? null); setToolStateError(null); }) .catch((err) => { console.error("Failed to load version manager status:", err); setToolStateError("Unable to reach version manager service"); setToolState(null); }); }, []); const updateSetting = useCallback(async (key: string, value: boolean | string) => { if (key === "cliproxyapi_url" && typeof value === "string" && value.trim() !== "") { if (!isValidUrl(value)) { setMessage({ type: "error", text: "Invalid URL format. Use http:// or https://" }); return; } } setSaving(true); setMessage(null); try { const res = await fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ [key]: value }), }); if (!res.ok) { throw new Error(`Server returned ${res.status}`); } await res.json(); setSettings((prev) => ({ ...prev, [key]: value })); setMessage({ type: "success", text: "Setting saved" }); } catch { setMessage({ type: "error", text: "Failed to save setting" }); } finally { setSaving(false); } }, []); const cpaEnabled = settings.cliproxyapi_fallback_enabled === true; const cpaUrl = settings.cliproxyapi_url || "http://127.0.0.1:8317"; const cpaCodes = settings.cliproxyapi_fallback_codes || "502,401,403,429,503"; const statusColor = toolState?.status === "running" ? "text-green-600 dark:text-green-400" : toolState?.status === "error" ? "text-red-600 dark:text-red-400" : "text-text-muted"; const statusIcon = toolState?.status === "running" ? "check_circle" : toolState?.status === "error" ? "error" : "help"; return (
{/* Migration banner — new lifecycle management lives in the Services page */}
info CLIProxyAPI lifecycle management (install, start, stop) has moved to{" "} Providers → Services . Fallback routing settings below remain here.
{message && (
{message.type === "success" ? "check_circle" : "error"} {message.text}
)}
swap_horiz

{t("cliproxyapiFallback")}

When enabled, failed requests are retried through CLIProxyAPI (localhost:8317)

updateSetting("cliproxyapi_fallback_enabled", checked)} />
{cpaEnabled && ( <>
updateSetting("cliproxyapi_url", e.target.value)} placeholder="http://127.0.0.1:8317" className="w-full" />
updateSetting("cliproxyapi_fallback_codes", e.target.value)} placeholder="502,401,403,429,503" className="w-full" />
)}

{t("cliproxyapiStatus")}

{loading ? (
progress_activity Loading...
) : toolStateError ? (

{toolStateError}

) : toolState ? (

Status

{statusIcon}

{toolState.status?.replace("_", " ") || "Unknown"}

Version

{toolState.installedVersion ? `v${toolState.installedVersion}` : "Not installed"}

Health

{toolState.healthStatus === "healthy" ? "Healthy" : toolState.healthStatus === "unhealthy" ? "Unhealthy" : "Unknown"}

Port

{toolState.port || 8317}

) : (

{t("cliproxyapiNotDetected")}

)}

{t("cliproxyapiImportAuthTitle")}

{t("cliproxyapiImportAuthDesc")}

{importResult ? (

{importResult}

) : null}
); }