feat(ui): add newbie-friendly UX improvements across dashboard

- Providers page: empty state with guided 'Add your first provider' card
- Providers page: home page shows only configured providers by default
- Settings: add intro text to Routing, Resilience, and AI tabs
- i18n: fix 'Api Key Mgmt' to 'API Key Management', remove duplicate key
- i18n: add keys for empty state, settings intros, free-only filter
This commit is contained in:
oyi77
2026-05-17 07:56:22 +07:00
parent 8a23c5527e
commit a4a870cda3
4 changed files with 88 additions and 12 deletions

View File

@@ -787,14 +787,31 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
{providerStats.map((item) => (
<ProviderOverviewCard
key={item.id}
item={item}
metrics={providerMetrics[item.provider.alias] || providerMetrics[item.id]}
onClick={() => setSelectedProvider(item)}
/>
))}
{providerStats
.filter((item) => item.total > 0)
.map((item) => (
<ProviderOverviewCard
key={item.id}
item={item}
metrics={providerMetrics[item.provider.alias] || providerMetrics[item.id]}
onClick={() => setSelectedProvider(item)}
/>
))}
{providerStats.filter((item) => item.total > 0).length === 0 && (
<div className="col-span-full py-8 text-center">
<span className="material-symbols-outlined text-[32px] text-text-muted/40">dns</span>
<p className="text-sm text-text-muted mt-2">
{t("noProvidersConfigured") || "No providers configured yet"}
</p>
<Link
href="/dashboard/providers"
className="inline-flex items-center gap-1 mt-2 text-xs text-primary hover:underline"
>
{t("addProvider") || "Add a provider"}{" "}
<span className="material-symbols-outlined text-[14px]">arrow_forward</span>
</Link>
</div>
)}
</div>
</Card>

View File

@@ -541,6 +541,40 @@ export default function ProvidersPage() {
);
}
const totalConfigured =
oauthProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length +
apiKeyProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length +
webCookieProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length +
localProviderEntriesAll.filter((e) => Number(e.stats?.total || 0) > 0).length;
if (totalConfigured === 0 && !searchQuery) {
return (
<div className="flex flex-col items-center justify-center py-20 text-center">
<div className="flex items-center justify-center size-16 rounded-full bg-primary/10 mb-4">
<span className="material-symbols-outlined text-[32px] text-primary">dns</span>
</div>
<h2 className="text-xl font-semibold text-text-main">
{t("addFirstProvider") || "Add your first provider"}
</h2>
<p className="text-sm text-text-muted mt-2 max-w-md">
{t("addFirstProviderDesc") ||
"Connect an AI provider to start routing requests through OmniRoute. You can use free providers, API keys, or OAuth accounts."}
</p>
<div className="flex items-center gap-3 mt-4">
<a
href="https://docs.omniroute.io/providers"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium rounded-lg border border-border text-text-muted hover:text-text-main hover:bg-bg-subtle transition-colors"
>
<span className="material-symbols-outlined text-[16px]">help</span>
{t("learnMore") || "Learn more"}
</a>
</div>
</div>
);
}
return (
<div className="flex flex-col gap-6">
{/* Search Bar */}

View File

@@ -93,6 +93,10 @@ export default function SettingsPage() {
{activeTab === "ai" && (
<div className="flex flex-col gap-6">
<p className="text-sm text-text-muted">
{t("aiSettingsIntro") ||
"AI-specific settings for thinking budget, model behavior, and compression."}
</p>
<ThinkingBudgetTab />
<Link
href="/dashboard/context/caveman"
@@ -124,6 +128,10 @@ export default function SettingsPage() {
{activeTab === "routing" && (
<div className="flex flex-col gap-6">
<p className="text-sm text-text-muted">
{t("routingSettingsIntro") ||
"Controls how your requests are routed, transformed, and sent to AI providers."}
</p>
<RoutingTab />
<ModelRoutingSection />
<ComboDefaultsTab />
@@ -132,7 +140,15 @@ export default function SettingsPage() {
</div>
)}
{activeTab === "resilience" && <ResilienceTab />}
{activeTab === "resilience" && (
<div className="flex flex-col gap-6">
<p className="text-sm text-text-muted">
{t("resilienceSettingsIntro") ||
"Automatic retry, cooldown, and fallback when providers fail."}
</p>
<ResilienceTab />
</div>
)}
{activeTab === "advanced" && (
<div className="flex flex-col gap-6">

View File

@@ -365,6 +365,10 @@
"apiKeyForCheck": "Api Key For Check",
"cloudRequestTimeout": "Cloud Request Timeout",
"showConfiguredOnly": "Show Configured Only",
"showFreeOnly": "Free only",
"addFirstProvider": "Add your first provider",
"addFirstProviderDesc": "Connect an AI provider to start routing requests through OmniRoute. You can use free providers, API keys, or OAuth accounts.",
"learnMore": "Learn more",
"includeDomains": "Include Domains",
"promptCache": "Prompt Cache",
"cloudConnected": "Cloud Connected",
@@ -405,7 +409,7 @@
"expirationBannerExpiringSoonDesc": "Expiration Banner Expiring Soon Desc",
"source": "Source",
"failed": "Failed",
"apiKeyMgmt": "Api Key Mgmt",
"apiKeyMgmt": "API Key Management",
"mcpQuickStartStep1": "Mcp Quick Start Step1",
"runTest": "Run Test",
"loadingFallbackChains": "Loading Fallback Chains",
@@ -905,6 +909,8 @@
"providersOverview": "Providers Overview",
"configuredOf": "{configured} configured of {total} available providers",
"noModelsAvailable": "No models available for this provider.",
"noProvidersConfigured": "No providers configured yet",
"addProvider": "Add a provider",
"configureFirst": "Configure a connection first in {providers}",
"configureProvider": "Configure Provider",
"modelAvailable": "{count} model available",
@@ -2618,7 +2624,6 @@
"welcomeDesc": "OmniRoute is your local AI API proxy. It routes requests to multiple AI providers with load balancing, failover, and usage tracking.",
"multiProvider": "Multi-Provider",
"usageTracking": "Usage Tracking",
"apiKeyMgmt": "API Key Mgmt",
"securityDesc": "Set a password to protect your dashboard, or skip for now.",
"providerDesc": "Connect your first AI provider. You can add more later.",
"apiKeyRequired": "API Key (required)",
@@ -2637,7 +2642,8 @@
"failedSetPassword": "Failed to set password. Try again.",
"failedAddProvider": "Failed to add provider. Try again.",
"connectionError": "Connection error. Please try again.",
"provider": "Provider"
"provider": "Provider",
"apiKeyHelp": "An API key is a password for AI services. Get one from your provider's website (e.g., platform.openai.com, console.anthropic.com)."
},
"providers": {
"title": "Providers",
@@ -3148,6 +3154,9 @@
"routing": "Routing",
"cache": "Cache",
"resilience": "Resilience",
"routingSettingsIntro": "Controls how your requests are routed, transformed, and sent to AI providers.",
"resilienceSettingsIntro": "Automatic retry, cooldown, and fallback when providers fail.",
"aiSettingsIntro": "AI-specific settings for thinking budget, model behavior, and compression.",
"systemPrompt": "System Prompt",
"thinkingBudget": "Thinking Budget",
"proxy": "Proxy",