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 <zhen@dkzhen.org>
This commit is contained in:
1 parent
9ca388972c
commit
0da61d8f7b
29 files changed
+561
-464
No files matched your search
@@ -245,19 +245,19 @@ export default function ProvidersPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex min-w-0 flex-col gap-6 px-1 sm:px-0">
|
||||
{/* OAuth Providers */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h2 className="text-lg sm:text-xl font-semibold flex items-center gap-2 leading-tight">
|
||||
OAuth Providers
|
||||
</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:items-center">
|
||||
<ModelAvailabilityBadge />
|
||||
<button
|
||||
onClick={() => handleBatchTest("oauth")}
|
||||
disabled={!!testingMode}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
|
||||
className={`flex w-full items-center justify-center gap-1.5 rounded-lg border px-3 py-2 text-xs font-medium transition-colors sm:w-auto sm:py-1.5 ${
|
||||
testingMode === "oauth"
|
||||
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
|
||||
: "bg-bg border-border text-text-muted hover:text-text-main hover:border-primary/40"
|
||||
@@ -274,7 +274,7 @@ export default function ProvidersPage() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{Object.entries(OAUTH_PROVIDERS).map(([key, info]) => (
|
||||
<ProviderCard
|
||||
key={key}
|
||||
@@ -288,16 +288,16 @@ export default function ProvidersPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Free & Free Tier Providers */}
|
||||
{/* Free Tier Providers */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
Free & Free Tier Providers
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h2 className="text-lg sm:text-xl font-semibold flex items-center gap-2 leading-tight">
|
||||
Free Tier Providers
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => handleBatchTest("free")}
|
||||
disabled={!!testingMode}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
|
||||
className={`flex w-full items-center justify-center gap-1.5 rounded-lg border px-3 py-2 text-xs font-medium transition-colors sm:w-auto sm:py-1.5 ${
|
||||
testingMode === "free"
|
||||
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
|
||||
: "bg-bg border-border text-text-muted hover:text-text-main hover:border-primary/40"
|
||||
@@ -313,7 +313,7 @@ export default function ProvidersPage() {
|
||||
{testingMode === "free" ? "Testing..." : "Test All"}
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{Object.entries(FREE_PROVIDERS).map(([key, info]) => (
|
||||
<ProviderCard
|
||||
key={key}
|
||||
@@ -339,14 +339,14 @@ export default function ProvidersPage() {
|
||||
|
||||
{/* API Key Providers — fixed list */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h2 className="text-lg sm:text-xl font-semibold flex items-center gap-2 leading-tight">
|
||||
API Key Providers{" "}
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => handleBatchTest("apikey")}
|
||||
disabled={!!testingMode}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium border transition-colors ${
|
||||
className={`flex w-full items-center justify-center gap-1.5 rounded-lg border px-3 py-2 text-xs font-medium transition-colors sm:w-auto sm:py-1.5 ${
|
||||
testingMode === "apikey"
|
||||
? "bg-primary/20 border-primary/40 text-primary animate-pulse"
|
||||
: "bg-bg border-border text-text-muted hover:text-text-main hover:border-primary/40"
|
||||
@@ -362,7 +362,7 @@ export default function ProvidersPage() {
|
||||
{testingMode === "apikey" ? "Testing..." : "Test All"}
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{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) */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h2 className="text-lg sm:text-xl font-semibold flex items-center gap-2 leading-tight">
|
||||
API Key Compatible Providers{" "}
|
||||
</h2>
|
||||
<div className="flex gap-2">
|
||||
<div className="grid grid-cols-1 gap-2 sm:flex sm:w-auto">
|
||||
{/* {(compatibleProviders.length > 0 || anthropicCompatibleProviders.length > 0) && (
|
||||
<button
|
||||
onClick={() => handleBatchTest("compatible")}
|
||||
@@ -426,6 +426,7 @@ export default function ProvidersPage() {
|
||||
size="sm"
|
||||
icon="add"
|
||||
onClick={() => setShowAddAnthropicCompatibleModal(true)}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
Add Anthropic Compatible
|
||||
</Button>
|
||||
@@ -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
|
||||
</Button>
|
||||
@@ -455,7 +456,7 @@ export default function ProvidersPage() {
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{[...compatibleProviders, ...anthropicCompatibleProviders].map(
|
||||
(info) => (
|
||||
<ApiKeyProviderCard
|
||||
@@ -494,12 +495,12 @@ export default function ProvidersPage() {
|
||||
{/* Test Results Modal */}
|
||||
{testResults && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-start justify-center pt-[10vh]"
|
||||
className="fixed inset-0 z-50 flex items-start justify-center px-3 pt-[6vh] sm:pt-[10vh]"
|
||||
onClick={() => setTestResults(null)}
|
||||
>
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
||||
<div
|
||||
className="relative bg-surface border border-border rounded-xl w-full max-w-[600px] max-h-[80vh] overflow-y-auto shadow-2xl"
|
||||
className="relative bg-surface border border-border rounded-xl w-full max-w-[600px] max-h-[86vh] sm:max-h-[80vh] overflow-y-auto shadow-2xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="sticky top-0 z-10 flex items-center justify-between px-5 py-3 border-b border-border bg-surface/95 backdrop-blur-sm rounded-t-xl">
|
||||
@@ -540,15 +541,15 @@ function ProviderCard({ providerId, provider, stats, authType, onToggle }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Link href={`/dashboard/providers/${providerId}`} className="group">
|
||||
<Link href={`/dashboard/providers/${providerId}`} className="group min-w-0">
|
||||
<Card
|
||||
padding="xs"
|
||||
className={`h-full hover:bg-black/[0.01] dark:hover:bg-white/[0.01] transition-colors cursor-pointer ${allDisabled ? "opacity-50" : ""}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex min-w-0 items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div
|
||||
className="size-8 rounded-lg flex items-center justify-center"
|
||||
className="size-8 shrink-0 rounded-lg flex items-center justify-center"
|
||||
style={{
|
||||
backgroundColor: `${provider.color?.length > 7 ? provider.color : provider.color + "15"}`,
|
||||
}}
|
||||
@@ -564,9 +565,9 @@ function ProviderCard({ providerId, provider, stats, authType, onToggle }) {
|
||||
fallbackColor={provider.color}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">{provider.name}</h3>
|
||||
<div className="flex items-center gap-2 text-xs flex-wrap">
|
||||
<div className="min-w-0">
|
||||
<h3 className="truncate font-semibold">{provider.name}</h3>
|
||||
<div className="flex min-w-0 items-center gap-1.5 text-xs flex-wrap">
|
||||
{allDisabled ? (
|
||||
<Badge variant="default" size="sm">
|
||||
<span className="flex items-center gap-1">
|
||||
@@ -589,10 +590,10 @@ function ProviderCard({ providerId, provider, stats, authType, onToggle }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{stats.total > 0 && (
|
||||
<div
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
className="opacity-100 transition-opacity sm:opacity-0 sm:group-hover:opacity-100"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -668,15 +669,15 @@ function ApiKeyProviderCard({
|
||||
};
|
||||
|
||||
return (
|
||||
<Link href={`/dashboard/providers/${providerId}`} className="group">
|
||||
<Link href={`/dashboard/providers/${providerId}`} className="group min-w-0">
|
||||
<Card
|
||||
padding="xs"
|
||||
className={`h-full hover:bg-black/[0.01] dark:hover:bg-white/[0.01] transition-colors cursor-pointer ${allDisabled ? "opacity-50" : ""}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex min-w-0 items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div
|
||||
className="size-8 rounded-lg flex items-center justify-center"
|
||||
className="size-8 shrink-0 rounded-lg flex items-center justify-center"
|
||||
style={{
|
||||
backgroundColor: `${provider.color?.length > 7 ? provider.color : provider.color + "15"}`,
|
||||
}}
|
||||
@@ -692,9 +693,9 @@ function ApiKeyProviderCard({
|
||||
fallbackColor={provider.color}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">{provider.name}</h3>
|
||||
<div className="flex items-center gap-2 text-xs flex-wrap">
|
||||
<div className="min-w-0">
|
||||
<h3 className="truncate font-semibold">{provider.name}</h3>
|
||||
<div className="flex min-w-0 items-center gap-1.5 text-xs flex-wrap">
|
||||
{allDisabled ? (
|
||||
<Badge variant="default" size="sm">
|
||||
<span className="flex items-center gap-1">
|
||||
@@ -727,10 +728,10 @@ function ApiKeyProviderCard({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{stats.total > 0 && (
|
||||
<div
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
className="opacity-100 transition-opacity sm:opacity-0 sm:group-hover:opacity-100"
|
||||
onClick={(e) => {
|
||||
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."
|
||||
/>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<Button
|
||||
onClick={handleValidate}
|
||||
disabled={!checkKey || validating || !formData.baseUrl.trim()}
|
||||
variant="secondary"
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{validating ? "Checking..." : "Check"}
|
||||
</Button>
|
||||
{renderValidationResult()}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col gap-2 sm:flex-row">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
fullWidth
|
||||
@@ -1108,17 +1110,18 @@ function AddAnthropicCompatibleModal({ isOpen, onClose, onCreated }) {
|
||||
placeholder="e.g. claude-3-opus"
|
||||
hint="If provider lacks /models endpoint, enter a model ID to validate via chat/completions instead."
|
||||
/>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<Button
|
||||
onClick={handleValidate}
|
||||
disabled={!checkKey || validating || !formData.baseUrl.trim()}
|
||||
variant="secondary"
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{validating ? "Checking..." : "Check"}
|
||||
</Button>
|
||||
{renderValidationResult()}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col gap-2 sm:flex-row">
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
fullWidth
|
||||
@@ -1170,9 +1173,9 @@ function ProviderTestResultsView({ results }) {
|
||||
}[mode] || mode;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex min-w-0 flex-col gap-3">
|
||||
{summary && (
|
||||
<div className="flex items-center gap-3 text-xs mb-1">
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs mb-1 sm:gap-3">
|
||||
<span className="text-text-muted">{modeLabel} Test</span>
|
||||
<span className="px-2 py-0.5 rounded bg-emerald-500/15 text-emerald-400 font-medium">
|
||||
{summary.passed} passed
|
||||
@@ -1182,7 +1185,7 @@ function ProviderTestResultsView({ results }) {
|
||||
{summary.failed} failed
|
||||
</span>
|
||||
)}
|
||||
<span className="text-text-muted ml-auto">
|
||||
<span className="text-text-muted sm:ml-auto">
|
||||
{summary.total} tested
|
||||
</span>
|
||||
</div>
|
||||
@@ -1190,24 +1193,28 @@ function ProviderTestResultsView({ results }) {
|
||||
{items.map((r, i) => (
|
||||
<div
|
||||
key={r.connectionId || i}
|
||||
className="flex items-center gap-2 text-xs px-3 py-2 rounded-lg bg-black/[0.03] dark:bg-white/[0.03]"
|
||||
className="flex min-w-0 flex-wrap items-center gap-2 rounded-lg bg-black/[0.03] px-3 py-2 text-xs dark:bg-white/[0.03] sm:flex-nowrap"
|
||||
>
|
||||
<span
|
||||
className={`material-symbols-outlined text-[16px] ${r.valid ? "text-emerald-500" : "text-red-500"}`}
|
||||
>
|
||||
{r.valid ? "check_circle" : "error"}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="font-medium">{r.connectionName}</span>
|
||||
<span className="text-text-muted ml-1.5">({r.provider})</span>
|
||||
<div className="min-w-0 flex-[1_1_160px]">
|
||||
<span className="block truncate font-medium sm:inline">
|
||||
{r.connectionName}
|
||||
</span>
|
||||
<span className="block truncate text-text-muted sm:ml-1.5 sm:inline">
|
||||
({r.provider})
|
||||
</span>
|
||||
</div>
|
||||
{r.latencyMs !== undefined && (
|
||||
<span className="text-text-muted font-mono tabular-nums">
|
||||
<span className="shrink-0 text-text-muted font-mono tabular-nums">
|
||||
{r.latencyMs}ms
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className={`text-[10px] uppercase font-bold px-1.5 py-0.5 rounded ${
|
||||
className={`shrink-0 text-[10px] uppercase font-bold px-1.5 py-0.5 rounded ${
|
||||
r.valid
|
||||
? "bg-emerald-500/15 text-emerald-400"
|
||||
: "bg-red-500/15 text-red-400"
|
||||
|
||||
Reference in new issue
Block a user