Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
PowerOff,
AlertTriangle,
Comment thread
Sang-it marked this conversation as resolved.
} from "lucide-react";
import { Search } from "lucide-react";
Comment thread
Sang-it marked this conversation as resolved.
import { useState, useMemo, useCallback } from "react";
import { toast } from "sonner";

Expand Down Expand Up @@ -119,13 +120,21 @@ export default function ApiKeysPage() {
id: string;
name: string | null;
} | null>(null);
const [search, setSearch] = useState("");

const sortedKeys = useMemo(() => {
if (!data) return [];
return [...data.apiKeys].toSorted(
const sorted = [...data.apiKeys].toSorted(
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
);
}, [data]);
if (!search.trim()) return sorted;
const q = search.toLowerCase();
return sorted.filter(
(k) =>
(k.name ?? "").toLowerCase().includes(q) ||
(k.start ?? k.prefix ?? "").toLowerCase().includes(q),
);
}, [data, search]);

const resetCreateDialog = useCallback(() => {
setKeyName("");
Expand Down Expand Up @@ -204,14 +213,25 @@ export default function ApiKeysPage() {
<>
<div className="flex flex-1 flex-col">
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6 px-4 lg:px-6">
<div className="flex flex-col justify-between gap-4 sm:flex-row sm:items-center">
<div className="flex flex-col justify-between gap-4 sm:flex-row sm:items-end">
<div className="space-y-1">
<h2 className="text-lg font-semibold tracking-tight">Manage API Keys</h2>
<p className="text-sm text-muted-foreground">
Create and manage API keys to authenticate requests to the Enfinyte Router.
</p>
</div>

<div className="flex items-center gap-2 shrink-0">
<div className="relative w-full sm:w-56">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search keys..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-9 h-9 text-sm"
/>
</div>

<Dialog open={isCreateOpen} onOpenChange={handleCreateOpenChange}>
<DialogTrigger asChild>
<Button className="gap-2 cursor-pointer self-start">
Expand Down Expand Up @@ -342,9 +362,10 @@ export default function ApiKeysPage() {
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>

<div className="rounded-lg border border-border bg-card">
<div className="rounded-lg border border-border bg-card overflow-x-auto">
{isLoading ? (
<Table className="table-fixed">
<TableHeader>
Expand Down Expand Up @@ -411,16 +432,16 @@ export default function ApiKeysPage() {
</Button>
</div>
) : (
<Table className="table-fixed">
<Table className="md:table-fixed">
<TableHeader>
<TableRow>
<TableHead className="pl-4 w-[25%]">Name</TableHead>
<TableHead className="w-[15%]">Key Preview</TableHead>
<TableHead className="w-[10%]">Status</TableHead>
<TableHead className="w-[14%]">Created</TableHead>
<TableHead className="w-[14%]">Last Used</TableHead>
<TableHead className="w-[10%]">Expires</TableHead>
<TableHead className="text-right pr-4 w-[12%]">Actions</TableHead>
<TableHead className="pl-4">Name</TableHead>
<TableHead className="hidden sm:table-cell">Key Preview</TableHead>
<TableHead>Status</TableHead>
<TableHead className="hidden lg:table-cell">Created</TableHead>
<TableHead className="hidden lg:table-cell">Last Used</TableHead>
<TableHead className="hidden md:table-cell">Expires</TableHead>
<TableHead className="text-right pr-4">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
Expand All @@ -433,10 +454,10 @@ export default function ApiKeysPage() {
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-accent text-foreground">
<KeyRound className="h-4 w-4" />
</div>
<span className="text-sm">{apiKey.name ?? "Unnamed"}</span>
<span className="text-sm truncate">{apiKey.name ?? "Unnamed"}</span>
</div>
</TableCell>
<TableCell>
<TableCell className="hidden sm:table-cell">
<code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-muted-foreground">
{apiKey.start ?? apiKey.prefix ?? "\u2014"}...
</code>
Expand All @@ -452,13 +473,13 @@ export default function ApiKeysPage() {
{STATUS_LABELS[status]}
</Badge>
</TableCell>
<TableCell className="text-xs text-muted-foreground">
<TableCell className="hidden lg:table-cell text-xs text-muted-foreground">
{formatRelative(apiKey.createdAt)}
</TableCell>
<TableCell className="text-xs text-muted-foreground">
<TableCell className="hidden lg:table-cell text-xs text-muted-foreground">
{formatRelative(apiKey.lastRequest)}
</TableCell>
<TableCell className="text-xs text-muted-foreground">
<TableCell className="hidden md:table-cell text-xs text-muted-foreground">
{formatExpiry(apiKey.expiresAt)}
</TableCell>
<TableCell className="text-right pr-4">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import type { Metadata } from "next";
import { SiteHeader } from "@/components/site-header";

export const metadata: Metadata = {
title: "Connections | Enfinyte Router",
title: "Providers | Enfinyte Router",
description:
"Configure and manage your AI provider connections for the Enfinyte Router.",
"Configure and manage your AI providers for the Enfinyte Router.",
};

export default function ConnectionsLayout({
Expand All @@ -14,7 +14,7 @@ export default function ConnectionsLayout({
}>) {
return (
<>
<SiteHeader title="Connections" />
<SiteHeader title="Providers" />
{children}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useMemo, useState } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { ChevronDown, ChevronUp, Eye, EyeOff, Loader2, Power, PowerOff } from "lucide-react";
import { ChevronDown, ChevronUp, Eye, EyeOff, Loader2, Power, PowerOff, Search } from "lucide-react";
import { PROVIDERS } from "@/lib/providers";
import { useGetAllSecrets, useAddSecret, useToggleProvider } from "@/lib/api/secrets";
import { Button } from "@/components/ui/button";
Expand All @@ -24,6 +24,7 @@ export default function ConnectionsPage() {
const [expandedCards, setExpandedCards] = useState<Record<string, boolean>>({});
const [showSecrets, setShowSecrets] = useState<Record<string, Record<string, boolean>>>({});
const [savingProviderId, setSavingProviderId] = useState<string | null>(null);
const [search, setSearch] = useState("");

const credentials = useMemo(() => {
const result: Record<string, Record<string, string>> = {};
Expand Down Expand Up @@ -112,16 +113,38 @@ export default function ConnectionsPage() {
);
};

const filteredProviders = useMemo(() => {
if (!search.trim()) return PROVIDERS;
const q = search.toLowerCase();
return PROVIDERS.filter(
(p) =>
p.name.toLowerCase().includes(q) ||
p.description.toLowerCase().includes(q) ||
p.models.some((m) => m.toLowerCase().includes(q)),
);
}, [search]);

return (
<>
<div className="flex flex-1 flex-col">
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6 px-4 lg:px-6">
<div className="space-y-1">
<h2 className="text-lg font-semibold tracking-tight">Manage Providers</h2>
<p className="text-sm text-muted-foreground">
Configure and manage your AI provider connections. Toggle providers on or off to
control routing.
</p>
<div className="flex flex-1 flex-col min-w-0 overflow-hidden">
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6 px-4 lg:px-6 overflow-y-auto overflow-x-hidden">
<div className="flex flex-col justify-between gap-4 sm:flex-row sm:items-end">
<div className="space-y-1">
<h2 className="text-lg font-semibold tracking-tight">Manage Providers</h2>
<p className="text-sm text-muted-foreground">
Configure and manage your AI provider connections. Toggle providers on or off to
control routing.
</p>
</div>
<div className="relative w-full sm:w-64 shrink-0">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search providers..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-9 h-9 text-sm"
/>
</div>
</div>

<div className="grid gap-4">
Expand All @@ -138,7 +161,16 @@ export default function ConnectionsPage() {
</div>
</div>
))
: PROVIDERS.map((provider) => {
: filteredProviders.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 px-4 text-center rounded-lg border border-border bg-card">
<Search className="h-8 w-8 text-muted-foreground mb-3" />
<h3 className="text-sm font-semibold">Provider not available</h3>
<p className="text-sm text-muted-foreground mt-1">
Contact the devs to request support for this provider.
</p>
</div>
Comment thread
Sang-it marked this conversation as resolved.
)
: filteredProviders.map((provider) => {
const secretData = secretsData?.providers?.[provider.id];
const isConfigured = !!secretData && secretData.fields?.length > 0;
const isEnabled = secretData?.enabled ?? false;
Expand All @@ -151,15 +183,15 @@ export default function ConnectionsPage() {
<div
key={provider.id}
className={cn(
"group rounded-lg border border-border bg-card transition-all duration-200",
"group rounded-lg border border-border bg-card transition-all duration-200 min-w-0",
isExpanded && "border-muted-foreground/30",
isConfigured && isEnabled && "border-primary/20",
)}
>
<button
type="button"
onClick={() => toggleCardExpansion(provider.id)}
className="flex w-full cursor-pointer items-center gap-4 px-5 py-4 text-left focus:outline-none"
className="flex w-full cursor-pointer items-center gap-3 sm:gap-4 px-3 sm:px-5 py-4 text-left focus:outline-none overflow-hidden"
>
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-accent text-foreground">
{provider.icon}
Expand Down Expand Up @@ -197,7 +229,7 @@ export default function ConnectionsPage() {
</p>
</div>

<div className="flex items-center gap-3">
<div className="flex items-center gap-2 sm:gap-3 shrink-0">
<div className="hidden sm:flex items-center gap-1">
{provider.models.slice(0, 3).map((model) => (
<span
Expand Down Expand Up @@ -256,7 +288,7 @@ export default function ConnectionsPage() {
</button>

{isExpanded && (
<div className="border-t border-border px-5 pb-5 pt-4 animate-in slide-in-from-top-2 duration-200">
<div className="border-t border-border px-3 sm:px-5 pb-5 pt-4 animate-in slide-in-from-top-2 duration-200">
{!isConfigured && (
<div className="mb-4 rounded-md bg-accent/50 p-3 text-xs text-muted-foreground">
Set up credentials to start routing through {provider.name}.
Expand Down Expand Up @@ -287,7 +319,7 @@ export default function ConnectionsPage() {
onChange={(e) =>
handleInputChange(provider.id, field.key, e.target.value)
}
className="bg-background border-border pr-10 font-mono text-xs h-9 placeholder:font-sans"
className="bg-background border-border pr-10 font-mono text-xs h-9 placeholder:font-sans w-full min-w-0"
/>
{field.type === "password" && (
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default function DashboardPage() {
<ToggleGroupItem value="7D">Last 7 days</ToggleGroupItem>
</ToggleGroup>
<Select value={interval} onValueChange={(value) => setInterval(value as AnalyticsInterval)}>
<SelectTrigger className="w-40 @[767px]/main:hidden" size="sm">
<SelectTrigger className="w-full sm:w-40 @[767px]/main:hidden" size="sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export default function PlaygroundLayout({
children: React.ReactNode;
}>) {
return (
<>
<div className="flex flex-col h-screen max-h-[100dvh] md:max-h-[calc(100dvh-1rem)] overflow-hidden">
<SiteHeader title="Playground" />
{children}
</>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ChatPlayground } from "@/components/playground/chat-playground";

export default function PlaygroundPage() {
return (
<div className="flex flex-1 flex-col h-[calc(100vh-var(--header-height))]">
<div className="flex flex-1 flex-col overflow-hidden">
<ChatPlayground />
</div>
);
Expand Down
Loading
Loading