From 0da61d8f7b04e23888e0cb0877956db52c9a1511 Mon Sep 17 00:00:00 2001 From: Zhen <80612068+DEYLNN@users.noreply.github.com> Date: Fri, 1 May 2026 16:34:07 +0700 Subject: [PATCH] Improve dashboard responsive layouts (#805) * Improve dashboard responsive layouts * Improve proxy pools mobile layout * Improve CLI tool card input responsiveness --------- Co-authored-by: Delynn Assistant --- .../dashboard/cli-tools/CLIToolsPageClient.js | 14 +- .../components/AntigravityToolCard.js | 30 ++--- .../cli-tools/components/ClaudeToolCard.js | 50 +++---- .../cli-tools/components/CodexToolCard.js | 54 ++++---- .../cli-tools/components/CopilotToolCard.js | 10 +- .../cli-tools/components/DroidToolCard.js | 42 +++--- .../cli-tools/components/HermesToolCard.js | 44 +++--- .../cli-tools/components/MitmServerCard.js | 28 ++-- .../cli-tools/components/MitmToolCard.js | 26 ++-- .../cli-tools/components/OpenClawToolCard.js | 50 +++---- .../cli-tools/components/OpenCodeToolCard.js | 48 +++---- src/app/(dashboard)/dashboard/combos/page.js | 44 +++--- .../dashboard/mitm/MitmPageClient.js | 8 +- src/app/(dashboard)/dashboard/profile/page.js | 125 +++++++++--------- .../dashboard/providers/[id]/ConnectionRow.js | 30 ++--- .../dashboard/providers/[id]/ModelRow.js | 22 +-- .../dashboard/providers/[id]/page.js | 53 ++++---- .../(dashboard)/dashboard/providers/page.js | 115 ++++++++-------- .../(dashboard)/dashboard/proxy-pools/page.js | 32 ++--- .../usage/components/OverviewCards.js | 18 +-- .../usage/components/ProviderLimits/index.js | 96 +++++++++++--- .../usage/components/ProviderTopology.js | 2 +- .../usage/components/RequestDetailsTab.js | 43 +++--- .../dashboard/usage/components/UsageChart.js | 6 +- src/app/(dashboard)/dashboard/usage/page.js | 3 +- src/shared/components/Header.js | 7 + src/shared/components/SegmentedControl.js | 4 +- src/shared/components/Sidebar.js | 1 + src/shared/components/UsageStats.js | 20 +-- 29 files changed, 561 insertions(+), 464 deletions(-) diff --git a/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js b/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js index f81658a3..d8ebc998 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js +++ b/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js @@ -191,11 +191,19 @@ export default function CLIToolsPageClient({ machineId }) { const mitmTools = Object.entries(MITM_TOOLS); return ( -
-
+
+
+

CLI Tools

+

Configure local coding tools to use your 9Router providers.

+
+
{regularTools.map(([toolId, tool]) => renderToolCard(toolId, tool))}
-
+
+
+ security +

MITM Tools

+
{mitmTools.map(([toolId, tool]) => ( ))} diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js index db6b1855..ae14fcf6 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js @@ -231,8 +231,8 @@ export default function AntigravityToolCard({ return ( -
-
+
+
-
+

{tool.name}

{isRunning ? ( Active @@ -290,7 +290,7 @@ export default function AntigravityToolCard({
{/* Start/Stop Button */} -
+
{isRunning ? ( @@ -370,7 +370,7 @@ export default function AntigravityToolCard({
))} -
+
{/* API Key */} -
- API Key - arrow_forward +
+ API Key + arrow_forward {apiKeys.length > 0 ? ( - setSelectedApiKey(e.target.value)} className="min-w-0 px-2 py-2 bg-surface rounded text-xs border border-border focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5"> {apiKeys.map((key) => )} ) : ( - + {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} )} @@ -336,19 +336,19 @@ export default function ClaudeToolCard({ {/* Model Mappings */} {tool.defaultModels.map((model) => ( -
- {model.name} - arrow_forward - onModelMappingChange(model.alias, e.target.value)} placeholder="provider/model-id" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> - +
+ {model.name} + arrow_forward + onModelMappingChange(model.alias, e.target.value)} placeholder="provider/model-id" className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> + {modelMappings[model.alias] && }
))} {/* CC Filter Naming */} -
- Filter naming - arrow_forward +
+ Filter naming + arrow_forward
)} -
+
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js index f46976f8..2c2d464d 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js @@ -192,13 +192,13 @@ model = "${effectiveSubagentModel}" return ( -
-
+
+
{tool.name} { e.target.style.display = "none"; }} />
-
+

{tool.name}

{configStatus === "configured" && Connected} {configStatus === "not_configured" && Not configured} @@ -269,10 +269,10 @@ model = "${effectiveSubagentModel}" const parsed = codexStatus.config.match(/base_url\s*=\s*"([^"]+)"/); const currentBaseUrl = parsed ? parsed[1] : null; return currentBaseUrl ? ( -
- Current - arrow_forward - +
+ Current + arrow_forward + {currentBaseUrl}
@@ -280,15 +280,15 @@ model = "${effectiveSubagentModel}" })()} {/* Base URL */} -
- Base URL - arrow_forward +
+ Base URL + arrow_forward setCustomBaseUrl(e.target.value)} placeholder="https://.../v1" - className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" + className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> {customBaseUrl && customBaseUrl !== `${baseUrl}/v1` && (
{/* API Key */} -
- API Key - arrow_forward +
+ API Key + arrow_forward {apiKeys.length > 0 ? ( - setSelectedApiKey(e.target.value)} className="min-w-0 px-2 py-2 bg-surface rounded text-xs border border-border focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5"> {apiKeys.map((key) => )} ) : ( - + {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} )}
{/* Model */} -
- Model - arrow_forward - setSelectedModel(e.target.value)} placeholder="provider/model-id" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> - +
+ Model + arrow_forward + setSelectedModel(e.target.value)} placeholder="provider/model-id" className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> + {selectedModel && }
{/* Subagent Model */} -
- Subagent Model - arrow_forward +
+ Subagent Model + arrow_forward setSubagentModel(e.target.value)} placeholder={selectedModel || "provider/model-id (defaults to main model)"} - className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" + className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> @@ -358,7 +358,7 @@ model = "${effectiveSubagentModel}"
)} -
+
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js index 8e8f0d38..b223fde1 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js @@ -234,17 +234,17 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a
)} -
+
setModelInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && addModel()} placeholder="provider/model-id" - className="flex-1 px-3 py-2 bg-bg-secondary rounded-lg text-sm border border-border focus:outline-none focus:ring-1 focus:ring-primary/50" + className="min-w-0 px-3 py-2 bg-bg-secondary rounded-lg text-sm border border-border focus:outline-none focus:ring-1 focus:ring-primary/50" /> - - +
@@ -258,7 +258,7 @@ export default function CopilotToolCard({ tool, isExpanded, onToggle, baseUrl, a
)} -
+
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js index 9cd890ac..52f6e49d 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js @@ -219,13 +219,13 @@ export default function DroidToolCard({ return ( -
-
+
+
{tool.name} { e.target.style.display = "none"; }} />
-
+

{tool.name}

{configStatus === "configured" && Connected} {configStatus === "not_configured" && Not configured} @@ -287,25 +287,25 @@ export default function DroidToolCard({
{/* Current Base URL */} {droidStatus?.settings?.customModels?.find(m => m.id?.startsWith("custom:9Router"))?.baseUrl && ( -
- Current - arrow_forward - +
+ Current + arrow_forward + {droidStatus.settings.customModels.find(m => m.id?.startsWith("custom:9Router")).baseUrl}
)} {/* Base URL */} -
- Base URL - arrow_forward +
+ Base URL + arrow_forward setCustomBaseUrl(e.target.value)} placeholder="https://.../v1" - className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" + className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> {customBaseUrl && customBaseUrl !== baseUrl && (
{/* API Key */} -
- API Key - arrow_forward +
+ API Key + arrow_forward {apiKeys.length > 0 ? ( - setSelectedApiKey(e.target.value)} className="min-w-0 px-2 py-2 bg-surface rounded text-xs border border-border focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5"> {apiKeys.map((key) => )} ) : ( - + {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} )}
{/* Models */} -
- +
+ Models {modelList.length > 0 && ({modelList.length})} - arrow_forward + arrow_forward
{/* Model list */} {modelList.length > 0 && ( @@ -357,7 +357,7 @@ export default function DroidToolCard({ onChange={(e) => setModelInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addModel(); } }} placeholder="provider/model-id" - className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" + className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" />
)} -
+
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js index aaafb609..443985d6 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js @@ -177,13 +177,13 @@ export default function HermesToolCard({ return ( -
-
+
+
{tool.name} { e.target.style.display = "none"; }} />
-
+

{tool.name}

{configStatus === "configured" && Connected} {configStatus === "not_configured" && Not configured} @@ -228,24 +228,24 @@ export default function HermesToolCard({ <>
{hermesStatus?.settings?.model?.base_url && ( -
- Current - arrow_forward - +
+ Current + arrow_forward + {hermesStatus.settings.model.base_url}
)} -
- Base URL - arrow_forward +
+ Base URL + arrow_forward setCustomBaseUrl(e.target.value)} placeholder="https://.../v1" - className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" + className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> {customBaseUrl && customBaseUrl !== baseUrl && (
-
- API Key - arrow_forward +
+ API Key + arrow_forward {apiKeys.length > 0 ? ( - setSelectedApiKey(e.target.value)} className="min-w-0 px-2 py-2 bg-surface rounded text-xs border border-border focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5"> {apiKeys.map((key) => )} ) : ( - + {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} )}
-
- Default Model - arrow_forward - setSelectedModel(e.target.value)} placeholder="provider/model-id" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> - +
+ Default Model + arrow_forward + setSelectedModel(e.target.value)} placeholder="provider/model-id" className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> + {selectedModel && }
@@ -284,7 +284,7 @@ export default function HermesToolCard({
)} -
+
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js index 8766f7b3..182ac520 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js @@ -122,8 +122,8 @@ export default function MitmServerCard({ apiKeys, cloudEnabled, onStatusChange }
{/* Header */} -
-
+
+
security MITM Server {isRunning ? ( @@ -132,7 +132,7 @@ export default function MitmServerCard({ apiKeys, cloudEnabled, onStatusChange } Stopped )}
-
+
{[ { label: "Cert", ok: status?.certExists }, { label: "Trusted", ok: status?.certTrusted }, @@ -160,9 +160,9 @@ export default function MitmServerCard({ apiKeys, cloudEnabled, onStatusChange } {/* Base URL + API Key — same row pattern as Claude Code / cli-tools */}
-
- 9Router Base URL - arrow_forward +
+ 9Router Base URL + arrow_forward
{!isRunning && ( -
- API Key - arrow_forward +
+ API Key + arrow_forward {/* Action buttons */} -
+
{status?.certExists && !status?.certTrusted && ( @@ -232,12 +232,12 @@ export default function MitmToolCard({ )} {/* Start / Stop DNS button */} -
+
{dnsActive ? (
{/* API Key */} -
- API Key - arrow_forward +
+ API Key + arrow_forward {apiKeys.length > 0 ? ( - setSelectedApiKey(e.target.value)} className="min-w-0 px-2 py-2 bg-surface rounded text-xs border border-border focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5"> {apiKeys.map((key) => )} ) : ( - + {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} )}
{/* Default Model */} -
- Default Model - arrow_forward - setSelectedModel(e.target.value)} placeholder="provider/model-id" className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" /> - +
+ Default Model + arrow_forward + setSelectedModel(e.target.value)} placeholder="provider/model-id" className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> + {selectedModel && }
@@ -333,15 +333,15 @@ export default function OpenClawToolCard({ {(openclawStatus.agents || []).filter(a => a.agentDir).map((agent) => (
Agent {agent.name || agent.id} - arrow_forward + arrow_forward setAgentModels(prev => ({ ...prev, [agent.id]: e.target.value }))} placeholder={`default (${selectedModel || "provider/model-id"})`} - className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" + className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> - + {agentModels[agent.id] && }
))} @@ -354,7 +354,7 @@ export default function OpenClawToolCard({
)} -
+
diff --git a/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js b/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js index 4a5a2662..6616e076 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js +++ b/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js @@ -189,13 +189,13 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, return ( -
-
+
+
{tool.name} { e.target.style.display = "none"; }} />
-
+

{tool.name}

{configStatus === "configured" && Connected} {configStatus === "not_configured" && Not configured} @@ -257,25 +257,25 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
{/* Current base URL */} {status?.config?.provider?.["9router"]?.options?.baseURL && ( -
- Current - arrow_forward - +
+ Current + arrow_forward + {status.config.provider["9router"].options.baseURL}
)} {/* Base URL */} -
- Base URL - arrow_forward +
+ Base URL + arrow_forward setCustomBaseUrl(e.target.value)} placeholder="https://.../v1" - className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" + className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> {customBaseUrl && customBaseUrl !== `${baseUrl}/v1` && (
{/* API Key */} -
- API Key - arrow_forward +
+ API Key + arrow_forward {apiKeys.length > 0 ? ( - setSelectedApiKey(e.target.value)} className="min-w-0 px-2 py-2 bg-surface rounded text-xs border border-border focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5"> {apiKeys.map((key) => )} ) : ( - + {cloudEnabled ? "No API keys - Create one in Keys page" : "sk_9router (default)"} )}
{/* Models */} -
+
Models arrow_forward
@@ -364,7 +364,7 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl, )) )}
-
+
{selectedModels.length > 0 && activeModel ? ( @@ -380,20 +380,20 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
{/* Subagent Model */} -
- Subagent Model - arrow_forward +
+ Subagent Model + arrow_forward setSubagentModel(e.target.value)} placeholder={selectedModel || "provider/model-id (defaults to main model)"} - className="flex-1 px-2 py-1.5 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50" + className="min-w-0 px-2 py-2 bg-surface rounded border border-border text-xs focus:outline-none focus:ring-1 focus:ring-primary/50 sm:py-1.5" /> @@ -416,7 +416,7 @@ export default function OpenCodeToolCard({ tool, isExpanded, onToggle, baseUrl,
)} -
+
diff --git a/src/app/(dashboard)/dashboard/combos/page.js b/src/app/(dashboard)/dashboard/combos/page.js index dfaa4661..182bab57 100644 --- a/src/app/(dashboard)/dashboard/combos/page.js +++ b/src/app/(dashboard)/dashboard/combos/page.js @@ -125,16 +125,16 @@ export default function CombosPage() { } return ( -
+
{/* Header */} -
-
+
+

Combos

Create model combos with fallback support

-
@@ -148,7 +148,7 @@ export default function CombosPage() {

No combos yet

Create model combos with fallback support

-
@@ -195,19 +195,19 @@ export default function CombosPage() { function ComboCard({ combo, copied, onCopy, onEdit, onDelete, roundRobinEnabled, onToggleRoundRobin }) { return ( -
-
+
+
layers
- {combo.name} -
+ {combo.name} +
{combo.models.length === 0 ? ( No models ) : ( combo.models.slice(0, 3).map((model, index) => ( - + {model} )) @@ -220,9 +220,9 @@ function ComboCard({ combo, copied, onCopy, onEdit, onDelete, roundRobinEnabled,
{/* Actions */} -
+
{/* Round Robin Toggle — always visible */} -
+
Round Robin
-
+
) : ( -
+
{models.map((model, index) => ( {/* Actions */} -
+
diff --git a/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js b/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js index 6849359f..f22c443a 100644 --- a/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js +++ b/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js @@ -75,7 +75,11 @@ export default function MitmPageClient() { const mitmTools = Object.entries(MITM_TOOLS); return ( -
+
+
+

MITM

+

Route supported IDE traffic through 9Router with local DNS interception.

+
{/* MITM Server Card */} {/* Tool Cards */} -
+
{mitmTools.map(([toolId, tool]) => ( +
{/* Local Mode Info */} -
-
-
- computer +
+
+
+ computer
-

Local Mode

-

Running on your machine

+

Local Mode

+

Running on your machine

-
+
{["light", "dark", "system"].map((option) => ( ))}
-
+
-

Database Location

-

~/.9router/db.json

+

Database Location

+

~/.9router/db.json

-
+
@@ -388,6 +389,7 @@ export default function ProfilePage() { icon="upload" onClick={() => importFileRef.current?.click()} disabled={dbLoading} + className="w-full sm:w-auto" > Import Backup @@ -410,16 +412,16 @@ export default function ProfilePage() { {/* Security */}
-
+
shield
-

Security

+

Security

-
-
-

Require login

-

+

+
+

Require login

+

When ON, dashboard requires password. When OFF, access without login.

@@ -433,7 +435,7 @@ export default function ProfilePage() {
{settings.hasPassword && (
- +
)} */} -
+
- +
- + {passStatus.message && ( -

+

{passStatus.message}

)}
-
@@ -492,16 +494,16 @@ export default function ProfilePage() { {/* Routing Preferences */}
-
+
route
-

Routing Strategy

+

Routing Strategy

-
-
-

Round Robin

-

+

+
+

Round Robin

+

Cycle through accounts to distribute load

@@ -514,10 +516,10 @@ export default function ProfilePage() { {/* Sticky Round Robin Limit */} {settings.fallbackStrategy === "round-robin" && ( -
-
-

Sticky Limit

-

+

+
+

Sticky Limit

+

Calls per account before switching

@@ -528,16 +530,16 @@ export default function ProfilePage() { value={settings.stickyRoundRobinLimit || 3} onChange={(e) => updateStickyLimit(e.target.value)} disabled={loading} - className="w-20 text-center" + className="w-16 sm:w-20 text-center shrink-0" />
)} {/* Combo Round Robin */} -
-
-

Combo Round Robin

-

+

+
+

Combo Round Robin

+

Cycle through providers in combos instead of always starting with first

@@ -559,17 +561,17 @@ export default function ProfilePage() { {/* Network */}
-
+
wifi
-

Network

+

Network

-
-
-

Outbound Proxy

-

Enable proxy for OAuth + provider outbound requests.

+
+
+

Outbound Proxy

+

Enable proxy for OAuth + provider outbound requests.

- + setProxyForm((prev) => ({ ...prev, outboundProxyUrl: e.target.value }))} disabled={loading || proxyLoading} /> -

Leave empty to inherit existing env proxy (if any).

+

Leave empty to inherit existing env proxy (if any).

- + setProxyForm((prev) => ({ ...prev, outboundNoProxy: e.target.value }))} disabled={loading || proxyLoading} /> -

Comma-separated hostnames/domains to bypass the proxy.

+

Comma-separated hostnames/domains to bypass the proxy.

-
+
-
@@ -620,7 +623,7 @@ export default function ProfilePage() { )} {proxyStatus.message && ( -

+

{proxyStatus.message}

)} @@ -630,15 +633,15 @@ export default function ProfilePage() { {/* Observability Settings */}
-
+
monitoring
-

Observability

+

Observability

-
-
-

Enable Observability

-

+

+
+

Enable Observability

+

Record request details for inspection in the logs view

@@ -651,7 +654,7 @@ export default function ProfilePage() { {/* App Info */} -
+

{APP_CONFIG.name} v{APP_CONFIG.version}

Local Mode - All data stored on your machine

diff --git a/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js b/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js index 257270d1..19e4334c 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js @@ -109,10 +109,10 @@ export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst }; return ( -
-
+
+
{/* Priority arrows */} -
+
- + {isOAuth ? "lock" : "key"}

{displayName}

-
+
{connection.isActive === false ? "disabled" : (effectiveStatus || "Unknown")} @@ -144,7 +144,7 @@ export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst )} {isCooldown && connection.isActive !== false && } {connection.lastError && connection.isActive !== false && ( - + {connection.lastError} )} @@ -155,16 +155,16 @@ export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst
{hasAnyProxy && (
- + {proxyDisplayText} {maskedProxyUrl && ( - + {maskedProxyUrl} )} {noProxyText && ( - + no_proxy: {noProxyText} )} @@ -172,14 +172,14 @@ export default function ConnectionRow({ connection, proxyPools, isOAuth, isFirst )}
-
-
+
+
{/* Proxy button with inline dropdown */} {(proxyPools || []).length > 0 && (
{showProxyDropdown && ( -
+
)} - - diff --git a/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js b/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js index 44d78c52..25d62c5f 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js @@ -14,24 +14,24 @@ export default function ModelRow({ model, fullModel, alias, copied, onCopy, test : undefined; return ( -
-
+
+
{testStatus === "ok" ? "check_circle" : testStatus === "error" ? "cancel" : "smart_toy"} -
- {fullModel} - {model.name && {model.name}} +
+ {fullModel} + {model.name && {model.name}}
{onTest && ( -
+
)} -
+
@@ -656,7 +656,7 @@ export default function ProviderDetailPage() { {/* Add model button — inline, same style as model chips */} @@ -816,6 +817,7 @@ export default function ProviderDetailPage() { variant="secondary" icon="edit" onClick={() => setShowEditNodeModal(true)} + className="w-full sm:w-auto" > Edit @@ -834,6 +836,7 @@ export default function ProviderDetailPage() { console.log("Error deleting provider node:", error); } }} + className="w-full sm:w-auto" > Delete @@ -852,9 +855,9 @@ export default function ProviderDetailPage() { ) : ( -
+

Connections

-
+
{/* Thinking config */} {/* {thinkingConfig && (
@@ -871,7 +874,7 @@ export default function ProviderDetailPage() {
)} */} {/* Round Robin toggle */} -
+
Round Robin No connections yet

Add your first connection to get started

{!isCompatible && ( -
+
{providerId === "iflow" && ( @@ -934,6 +938,7 @@ export default function ProviderDetailPage() { size="sm" icon="add" onClick={() => isOAuth ? setShowOAuthModal(true) : setShowAddApiKeyModal(true)} + className="w-full sm:w-auto" > Add @@ -946,7 +951,7 @@ export default function ProviderDetailPage() { {/* Models */} -
+

{"Available Models"}

diff --git a/src/app/(dashboard)/dashboard/providers/page.js b/src/app/(dashboard)/dashboard/providers/page.js index b129ee8b..70ef4f98 100644 --- a/src/app/(dashboard)/dashboard/providers/page.js +++ b/src/app/(dashboard)/dashboard/providers/page.js @@ -245,19 +245,19 @@ export default function ProvidersPage() { } return ( -
+
{/* OAuth Providers */}
-
-

+
+

OAuth Providers

-
+
-
+
{Object.entries(OAUTH_PROVIDERS).map(([key, info]) => (
- {/* Free & Free Tier Providers */} + {/* Free Tier Providers */}
-
-

- Free & Free Tier Providers +
+

+ Free Tier Providers

-
+
{Object.entries(FREE_PROVIDERS).map(([key, info]) => ( -
-

+
+

API Key Providers{" "}

-
+
{Object.entries(APIKEY_PROVIDERS) .filter(([, info]) => (info.serviceKinds ?? ["llm"]).includes("llm")) .map(([key, info]) => ( @@ -401,11 +401,11 @@ export default function ProvidersPage() { {/* API Key Compatible Providers — dynamic (OpenAI/Anthropic compatible) */}
-
-

+
+

API Key Compatible Providers{" "}

-
+
{/* {(compatibleProviders.length > 0 || anthropicCompatibleProviders.length > 0) && ( @@ -434,7 +435,7 @@ export default function ProvidersPage() { variant="secondary" icon="add" onClick={() => setShowAddCompatibleModal(true)} - className="!bg-white !text-black hover:!bg-gray-100" + className="w-full !bg-white !text-black hover:!bg-gray-100 sm:w-auto" > Add OpenAI Compatible @@ -455,7 +456,7 @@ export default function ProvidersPage() {

) : ( -
+
{[...compatibleProviders, ...anthropicCompatibleProviders].map( (info) => ( setTestResults(null)} >
e.stopPropagation()} >
@@ -540,15 +541,15 @@ function ProviderCard({ providerId, provider, stats, authType, onToggle }) { }; return ( - + -
-
+
+
7 ? provider.color : provider.color + "15"}`, }} @@ -564,9 +565,9 @@ function ProviderCard({ providerId, provider, stats, authType, onToggle }) { fallbackColor={provider.color} />
-
-

{provider.name}

-
+
+

{provider.name}

+
{allDisabled ? ( @@ -589,10 +590,10 @@ function ProviderCard({ providerId, provider, stats, authType, onToggle }) {
-
+
{stats.total > 0 && (
{ e.preventDefault(); e.stopPropagation(); @@ -668,15 +669,15 @@ function ApiKeyProviderCard({ }; return ( - + -
-
+
+
7 ? provider.color : provider.color + "15"}`, }} @@ -692,9 +693,9 @@ function ApiKeyProviderCard({ fallbackColor={provider.color} />
-
-

{provider.name}

-
+
+

{provider.name}

+
{allDisabled ? ( @@ -727,10 +728,10 @@ function ApiKeyProviderCard({
-
+
{stats.total > 0 && (
{ e.preventDefault(); e.stopPropagation(); @@ -927,17 +928,18 @@ function AddOpenAICompatibleModal({ isOpen, onClose, onCreated }) { placeholder="e.g. gpt-4, claude-3-opus" hint="If provider lacks /models endpoint, enter a model ID to validate via chat/completions instead." /> -
+
{renderValidationResult()}
-
+
{renderValidationResult()}
-
+
@@ -354,8 +354,8 @@ export default function ProxyPoolsPage() {
-
-
+
+
Total: {proxyPools.length} Active: {activeCount}
@@ -372,10 +372,10 @@ export default function ProxyPoolsPage() { ) : (
{proxyPools.map((pool) => ( -
+
-

{pool.name}

+

{pool.name}

{pool.testStatus || "unknown"} @@ -399,7 +399,7 @@ export default function ProxyPoolsPage() {

-
+
-
+
@@ -497,7 +497,7 @@ export default function ProxyPoolsPage() { placeholder="my-relay" hint="Unique name for your Vercel project. Leave empty for auto-generated name." /> -
+
+ + {providerMenuOpen && ( + <> + +
+
+ {providerOptions.map((provider) => ( + + ))} +
+
+ + )} +
{/* Auto-refresh toggle */} @@ -236,7 +237,7 @@ export default function RequestDetailsTab() {
- +
@@ -270,13 +271,13 @@ export default function RequestDetailsTab() { key={`${detail.id}-${index}`} className="border-b border-black/5 dark:border-white/5 last:border-b-0 hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors" > - - -
Timestamp + {new Date(detail.timestamp).toLocaleString()} + {detail.model} + {getProviderName(detail.provider, providerNameCache)} @@ -330,10 +331,10 @@ export default function RequestDetailsTab() { > {selectedDetail && (
-
+
ID:{" "} - {selectedDetail.id} + {selectedDetail.id}
Timestamp:{" "} @@ -378,14 +379,14 @@ export default function RequestDetailsTab() {
-
+                
                   {JSON.stringify(selectedDetail.request, null, 2)}
                 
{selectedDetail.providerRequest && ( -
+                  
                     {JSON.stringify(selectedDetail.providerRequest, null, 2)}
                   
@@ -393,7 +394,7 @@ export default function RequestDetailsTab() { {selectedDetail.providerResponse && ( -
+                  
                     {typeof selectedDetail.providerResponse === 'object'
                       ? JSON.stringify(selectedDetail.providerResponse, null, 2)
                       : selectedDetail.providerResponse
@@ -409,7 +410,7 @@ export default function RequestDetailsTab() {
                       psychology
                       Thinking Process
                     
-                    
+                    
                       {selectedDetail.response.thinking}
                     
@@ -418,7 +419,7 @@ export default function RequestDetailsTab() {

Content

-
+                
                   {selectedDetail.response?.content || "[No content]"}
                 
diff --git a/src/app/(dashboard)/dashboard/usage/components/UsageChart.js b/src/app/(dashboard)/dashboard/usage/components/UsageChart.js index f4941f65..14a19c42 100644 --- a/src/app/(dashboard)/dashboard/usage/components/UsageChart.js +++ b/src/app/(dashboard)/dashboard/usage/components/UsageChart.js @@ -49,8 +49,8 @@ export default function UsageChart({ period = "7d" }) { const hasData = data.some((d) => d.tokens > 0 || d.cost > 0); return ( - -
+ +