Improve mobile layouts and restore Cloudflare provider (#840)
Co-authored-by: Delynn Assistant <zhen@dkzhen.org>
This commit is contained in:
1 parent
ef3abeacf1
commit
14ff538f2e
20 files changed
+223
-210
No files matched your search
@@ -16,9 +16,9 @@ import { getTtsVoicesForModel } from "open-sse/config/ttsModels.js";
|
||||
// Shared row layout — defined outside components to avoid re-mount on re-render
|
||||
function Row({ label, children }) {
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-text-muted w-20 shrink-0">{label}</span>
|
||||
<div className="flex-1">{children}</div>
|
||||
<div className="flex min-w-0 flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-3">
|
||||
<span className="w-full text-xs font-medium text-text-muted sm:w-20 sm:shrink-0">{label}</span>
|
||||
<div className="w-full min-w-0 flex-1">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -230,11 +230,11 @@ function EmbeddingExampleCard({ providerId, customAlias }) {
|
||||
|
||||
{/* Endpoint */}
|
||||
<Row label="Endpoint">
|
||||
<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">
|
||||
<input
|
||||
value={endpoint}
|
||||
onChange={(e) => useTunnel ? setTunnelEndpoint(e.target.value) : setLocalEndpoint(e.target.value)}
|
||||
className="flex-1 px-3 py-1.5 text-sm border border-border rounded-lg bg-background focus:outline-none focus:border-primary font-mono"
|
||||
className="w-full min-w-0 flex-1 px-3 py-1.5 text-sm border border-border rounded-lg bg-background focus:outline-none focus:border-primary font-mono"
|
||||
placeholder="http://localhost:3000"
|
||||
/>
|
||||
{/* Tunnel toggle — only show if tunnel URL is available */}
|
||||
@@ -298,12 +298,12 @@ function EmbeddingExampleCard({ providerId, customAlias }) {
|
||||
|
||||
{/* Curl + Run */}
|
||||
<div className="mt-1">
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between mb-1.5">
|
||||
<span className="text-xs font-semibold text-text-muted uppercase tracking-wider">Request</span>
|
||||
<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">
|
||||
<button
|
||||
onClick={() => copyCurl(curlSnippet)}
|
||||
className="flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
className="inline-flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">{copiedCurl ? "check" : "content_copy"}</span>
|
||||
{copiedCurl ? "Copied" : "Copy"}
|
||||
@@ -311,7 +311,7 @@ function EmbeddingExampleCard({ providerId, customAlias }) {
|
||||
<button
|
||||
onClick={handleRun}
|
||||
disabled={running || !input.trim() || !modelFull}
|
||||
className="flex items-center gap-1.5 px-3 py-1 rounded-lg bg-primary text-white text-xs font-medium hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="flex w-full sm:w-auto items-center justify-center gap-1.5 px-3 py-1 rounded-lg bg-primary text-white text-xs font-medium hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]" style={running ? { animation: "spin 1s linear infinite" } : undefined}>
|
||||
play_arrow
|
||||
@@ -320,7 +320,7 @@ function EmbeddingExampleCard({ providerId, customAlias }) {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre">{curlSnippet}</pre>
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre-wrap break-all">{curlSnippet}</pre>
|
||||
</div>
|
||||
|
||||
{/* Error */}
|
||||
@@ -328,21 +328,21 @@ function EmbeddingExampleCard({ providerId, customAlias }) {
|
||||
|
||||
{/* Response — default example or real result */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between mb-1.5">
|
||||
<span className="text-xs font-semibold text-text-muted uppercase tracking-wider">
|
||||
Response {result && <span className="font-normal normal-case">⚡ {result.latencyMs}ms</span>}
|
||||
</span>
|
||||
{result && (
|
||||
<button
|
||||
onClick={() => copyRes(resultJson)}
|
||||
className="flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
className="inline-flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">{copiedRes ? "check" : "content_copy"}</span>
|
||||
{copiedRes ? "Copied" : "Copy"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre opacity-70">
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre-wrap break-all opacity-70">
|
||||
{formatResultJson(result?.data)}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -567,8 +567,8 @@ function TtsExampleCard({ providerId }) {
|
||||
<div className="flex flex-col gap-2.5">
|
||||
{/* Endpoint + API Key as read-only text */}
|
||||
<Row label="Endpoint">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex-1 px-3 py-1.5 text-sm font-mono text-text-main bg-sidebar rounded-lg truncate">
|
||||
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:items-center">
|
||||
<span className="w-full min-w-0 flex-1 px-3 py-1.5 text-sm font-mono text-text-main bg-sidebar rounded-lg truncate">
|
||||
{endpoint}/v1/audio/speech
|
||||
</span>
|
||||
{tunnelEndpoint && (
|
||||
@@ -611,10 +611,10 @@ function TtsExampleCard({ providerId }) {
|
||||
{/* Language row + Browse button (edge-tts, local-device, elevenlabs) */}
|
||||
{config.hasBrowseButton && (
|
||||
<Row label="Language">
|
||||
<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">
|
||||
<button
|
||||
onClick={openModal}
|
||||
className="flex-1 px-3 py-1.5 text-sm border border-border rounded-lg bg-background font-mono truncate text-left hover:border-primary/40 transition-colors"
|
||||
className="w-full min-w-0 flex-1 px-3 py-1.5 text-sm border border-border rounded-lg bg-background font-mono truncate text-left hover:border-primary/40 transition-colors"
|
||||
>
|
||||
{selectedLang
|
||||
? <span className="text-text-main">{languages.find((l) => l.code === selectedLang)?.name || selectedLang}</span>
|
||||
@@ -622,7 +622,7 @@ function TtsExampleCard({ providerId }) {
|
||||
</button>
|
||||
<button
|
||||
onClick={openModal}
|
||||
className="flex items-center gap-1 text-xs px-2.5 py-1.5 rounded-lg border border-border text-text-muted hover:text-primary hover:border-primary/40 transition-colors shrink-0"
|
||||
className="flex w-full items-center justify-center gap-1 text-xs px-2.5 py-1.5 rounded-lg border border-border text-text-muted hover:text-primary hover:border-primary/40 transition-colors sm:w-auto sm:shrink-0"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">language</span>
|
||||
Select language
|
||||
@@ -743,12 +743,12 @@ function TtsExampleCard({ providerId }) {
|
||||
|
||||
{/* Curl + Run */}
|
||||
<div className="mt-1">
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between mb-1.5">
|
||||
<span className="text-xs font-semibold text-text-muted uppercase tracking-wider">Request</span>
|
||||
<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">
|
||||
<button
|
||||
onClick={() => copyCurl(curlSnippet)}
|
||||
className="flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
className="inline-flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">{copiedCurl ? "check" : "content_copy"}</span>
|
||||
{copiedCurl ? "Copied" : "Copy"}
|
||||
@@ -756,7 +756,7 @@ function TtsExampleCard({ providerId }) {
|
||||
<button
|
||||
onClick={handleRun}
|
||||
disabled={running || !input.trim() || !modelFull}
|
||||
className="flex items-center gap-1.5 px-3 py-1 rounded-lg bg-primary text-white text-xs font-medium hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="flex w-full sm:w-auto items-center justify-center gap-1.5 px-3 py-1 rounded-lg bg-primary text-white text-xs font-medium hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]" style={running ? { animation: "spin 1s linear infinite" } : undefined}>
|
||||
play_arrow
|
||||
@@ -765,7 +765,7 @@ function TtsExampleCard({ providerId }) {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre">{curlSnippet}</pre>
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre-wrap break-all">{curlSnippet}</pre>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-500 break-words">{error}</p>}
|
||||
@@ -773,11 +773,11 @@ function TtsExampleCard({ providerId }) {
|
||||
{/* Audio player */}
|
||||
{audioUrl ? (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between mb-1.5">
|
||||
<span className="text-xs font-semibold text-text-muted uppercase tracking-wider">
|
||||
Response {latency && <span className="font-normal normal-case">⚡ {latency}ms</span>}
|
||||
</span>
|
||||
<a href={audioUrl} download="speech.mp3" className="flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors">
|
||||
<a href={audioUrl} download="speech.mp3" className="inline-flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors">
|
||||
<span className="material-symbols-outlined text-[14px]">download</span>
|
||||
Download
|
||||
</a>
|
||||
@@ -787,7 +787,7 @@ function TtsExampleCard({ providerId }) {
|
||||
{/* JSON Response (if format is json) */}
|
||||
{jsonResponse && (
|
||||
<div className="mt-3">
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between mb-1.5">
|
||||
<span className="text-xs font-semibold text-text-muted uppercase tracking-wider">JSON Response</span>
|
||||
</div>
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre-wrap break-all">
|
||||
@@ -802,7 +802,7 @@ function TtsExampleCard({ providerId }) {
|
||||
) : (
|
||||
<div>
|
||||
<span className="text-xs font-semibold text-text-muted uppercase tracking-wider">Response</span>
|
||||
<pre className="mt-1.5 bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre opacity-50">{DEFAULT_TTS_RESPONSE_EXAMPLE}</pre>
|
||||
<pre className="mt-1.5 bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre-wrap break-all opacity-50">{DEFAULT_TTS_RESPONSE_EXAMPLE}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -811,7 +811,7 @@ function TtsExampleCard({ providerId }) {
|
||||
{/* Country Picker Modal */}
|
||||
{modalOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center"
|
||||
className="fixed inset-0 z-50 flex items-end justify-center sm:items-center"
|
||||
style={{ backgroundColor: "rgba(0,0,0,0.6)", backdropFilter: "blur(2px)" }}
|
||||
onClick={() => setModalOpen(false)}
|
||||
>
|
||||
@@ -1077,8 +1077,8 @@ function GenericExampleCard({ providerId, kind }) {
|
||||
|
||||
{/* Endpoint */}
|
||||
<Row label="Endpoint">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex-1 px-3 py-1.5 text-sm font-mono text-text-main bg-sidebar rounded-lg truncate">
|
||||
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:items-center">
|
||||
<span className="w-full min-w-0 flex-1 px-3 py-1.5 text-sm font-mono text-text-main bg-sidebar rounded-lg truncate">
|
||||
{endpoint}{apiPath}
|
||||
</span>
|
||||
{tunnelEndpoint && (
|
||||
@@ -1232,12 +1232,12 @@ function GenericExampleCard({ providerId, kind }) {
|
||||
|
||||
{/* Curl + Run */}
|
||||
<div className="mt-1">
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between mb-1.5">
|
||||
<span className="text-xs font-semibold text-text-muted uppercase tracking-wider">Request</span>
|
||||
<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">
|
||||
<button
|
||||
onClick={() => copyCurl(curlSnippet)}
|
||||
className="flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
className="inline-flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">{copiedCurl ? "check" : "content_copy"}</span>
|
||||
{copiedCurl ? "Copied" : "Copy"}
|
||||
@@ -1245,7 +1245,7 @@ function GenericExampleCard({ providerId, kind }) {
|
||||
<button
|
||||
onClick={handleRun}
|
||||
disabled={running || !input.trim() || !modelFull}
|
||||
className="flex items-center gap-1.5 px-3 py-1 rounded-lg bg-primary text-white text-xs font-medium hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="flex w-full sm:w-auto items-center justify-center gap-1.5 px-3 py-1 rounded-lg bg-primary text-white text-xs font-medium hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]" style={running ? { animation: "spin 1s linear infinite" } : undefined}>
|
||||
play_arrow
|
||||
@@ -1254,12 +1254,12 @@ function GenericExampleCard({ providerId, kind }) {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre">{curlSnippet}</pre>
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre-wrap break-all">{curlSnippet}</pre>
|
||||
</div>
|
||||
|
||||
{/* Streaming progress */}
|
||||
{(running || progress) && useStreaming && (
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-lg bg-sidebar border border-border">
|
||||
<div className="flex flex-col gap-2 px-3 py-2 rounded-lg bg-sidebar border border-border sm:flex-row sm:items-center sm:gap-3">
|
||||
<span className="material-symbols-outlined text-[16px] text-primary" style={running ? { animation: "spin 1s linear infinite" } : undefined}>
|
||||
{running ? "progress_activity" : "check_circle"}
|
||||
</span>
|
||||
@@ -1287,21 +1287,21 @@ function GenericExampleCard({ providerId, kind }) {
|
||||
|
||||
{/* Response */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between mb-1.5">
|
||||
<span className="text-xs font-semibold text-text-muted uppercase tracking-wider">
|
||||
Response {result && <span className="font-normal normal-case">⚡ {result.latencyMs}ms</span>}
|
||||
</span>
|
||||
{result && (
|
||||
<button
|
||||
onClick={() => copyRes(resultJson)}
|
||||
className="flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
className="inline-flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">{copiedRes ? "check" : "content_copy"}</span>
|
||||
{copiedRes ? "Copied" : "Copy"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre opacity-70">
|
||||
<pre className="bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre-wrap break-all opacity-70">
|
||||
{result ? resultJson : exConfig.defaultResponse}
|
||||
</pre>
|
||||
{kind === "image" && (binaryImageUrl || result?.data?.data?.[0]) && (
|
||||
@@ -1310,7 +1310,7 @@ function GenericExampleCard({ providerId, kind }) {
|
||||
<a
|
||||
href={binaryImageUrl || (result?.data?.data?.[0]?.b64_json ? `data:image/png;base64,${result.data.data[0].b64_json}` : result?.data?.data?.[0]?.url || "")}
|
||||
download="image.png"
|
||||
className="flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
className="inline-flex items-center gap-1 text-xs text-text-muted hover:text-primary transition-colors"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px]">download</span>
|
||||
Download
|
||||
@@ -1396,7 +1396,7 @@ export default function MediaProviderDetailPage() {
|
||||
</Link>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
|
||||
<div className="size-12 rounded-lg flex items-center justify-center shrink-0" style={{ backgroundColor: `${provider.color}15` }}>
|
||||
<ProviderIcon
|
||||
src={`/providers/${provider.id}.png`}
|
||||
@@ -1408,7 +1408,7 @@ export default function MediaProviderDetailPage() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
||||
<h1 className="text-3xl font-semibold tracking-tight">{provider.name}</h1>
|
||||
{!isCustom && provider.notice?.apiKeyUrl && (
|
||||
<a
|
||||
@@ -1432,7 +1432,7 @@ export default function MediaProviderDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
{isCustom && (
|
||||
<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">
|
||||
<Button size="sm" variant="secondary" icon="edit" onClick={() => setShowEditModal(true)}>
|
||||
Edit
|
||||
</Button>
|
||||
|
||||
@@ -43,7 +43,7 @@ function MediaProviderCard({ provider, kind, connections, isCustom }) {
|
||||
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 gap-3">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div
|
||||
className="size-8 rounded-lg flex items-center justify-center shrink-0"
|
||||
style={{ backgroundColor: `${provider.color?.length > 7 ? provider.color : (provider.color ?? "#888") + "15"}` }}
|
||||
|
||||
@@ -44,7 +44,7 @@ function ProviderPickerModal({ isOpen, onClose, onPick, kind, currentIds, connec
|
||||
No connected providers available. Add a connection first in the {KIND_LABELS[kind]} section.
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2 max-h-[400px] overflow-y-auto">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-2 max-h-[400px] overflow-y-auto">
|
||||
{providers.map((p) => {
|
||||
const already = currentIds.includes(p.id);
|
||||
return (
|
||||
@@ -218,7 +218,7 @@ export default function ComboDetailPage() {
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<Link href="/dashboard/media-providers/web" className="text-text-muted hover:text-primary">
|
||||
<span className="material-symbols-outlined">arrow_back</span>
|
||||
@@ -256,7 +256,7 @@ export default function ComboDetailPage() {
|
||||
|
||||
{/* Providers Card */}
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-3">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Providers</h2>
|
||||
<p className="text-xs text-text-muted">Tried in order (top-down) or rotated when round-robin is on.</p>
|
||||
@@ -304,7 +304,7 @@ export default function ComboDetailPage() {
|
||||
{/* Test Example Card */}
|
||||
{combo.kind && (
|
||||
<Card>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-3">
|
||||
<h2 className="text-lg font-semibold">Test Example</h2>
|
||||
<Button size="sm" icon="play_arrow" onClick={handleTest} disabled={testing || providers.length === 0}>
|
||||
{testing ? "Running..." : "Run"}
|
||||
|
||||
@@ -39,7 +39,7 @@ function ProviderCard({ provider, kind, connections }) {
|
||||
return (
|
||||
<Link href={`/dashboard/media-providers/${kind}/${provider.id}`} className="group">
|
||||
<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 gap-3">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div
|
||||
className="size-8 rounded-lg flex items-center justify-center shrink-0"
|
||||
style={{ backgroundColor: `${provider.color?.length > 7 ? provider.color : (provider.color ?? "#888") + "15"}` }}
|
||||
@@ -72,11 +72,11 @@ function ComboList({ combos }) {
|
||||
{combos.map((combo) => (
|
||||
<Link key={combo.id} href={`/dashboard/media-providers/web/combo/${combo.id}`}>
|
||||
<Card padding="xs" className="hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors cursor-pointer">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<span className="material-symbols-outlined text-primary text-[18px]">layers</span>
|
||||
<code className="text-sm font-mono font-medium flex-1 truncate">{combo.name}</code>
|
||||
{/* Provider icons preview */}
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<div className="flex flex-wrap items-center gap-1 sm:shrink-0">
|
||||
{combo.models.slice(0, 6).map((pid, i) => {
|
||||
const p = AI_PROVIDERS[pid];
|
||||
return (
|
||||
@@ -110,8 +110,8 @@ function Section({ title, icon, kind, providers, connections, combos, onCreateCo
|
||||
return (
|
||||
<div>
|
||||
{/* Header — title left, Create Combo right */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-3">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="material-symbols-outlined text-primary">{icon}</span>
|
||||
<h2 className="text-base font-semibold">{title}</h2>
|
||||
<span className="text-xs text-text-muted">({providers.length} providers · {combos.length} combos)</span>
|
||||
|
||||
Reference in new issue
Block a user