cba5d9c3ac
Sticky sidebar with back-to-app link. Overview: user counts, recipe photos, AI calls/month. User management with role/tier editing. Audit log (all admin actions). Storage page with photo breakdown by tier. AI config showing DB vs .env key source. Site settings page to override .env values at runtime (encrypted in DB).
109 lines
4.6 KiB
TypeScript
109 lines
4.6 KiB
TypeScript
import type { Metadata } from "next";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { CheckCircle, XCircle, Database, Cpu } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { getAllSiteSettings } from "@/lib/site-settings";
|
|
|
|
export const metadata: Metadata = { title: "AI Config" };
|
|
|
|
function resolveProvider(settings: Record<string, { value: string | null }>) {
|
|
if (settings["OPENROUTER_API_KEY"]?.value) return { provider: "OpenRouter", description: "Routes to many models via openrouter.ai" };
|
|
if (settings["OPENAI_API_KEY"]?.value) return { provider: "OpenAI", description: "Direct OpenAI API (GPT-4 etc.)" };
|
|
if (settings["ANTHROPIC_API_KEY"]?.value) return { provider: "Anthropic", description: "Direct Anthropic API (Claude models)" };
|
|
const url = settings["OLLAMA_BASE_URL"]?.value || "http://localhost:11434";
|
|
return { provider: "Ollama", description: `Local inference at ${url}` };
|
|
}
|
|
|
|
export default async function AdminAiConfigPage() {
|
|
const settings = await getAllSiteSettings();
|
|
const active = resolveProvider(settings);
|
|
|
|
const keyRows = [
|
|
{ key: "OPENROUTER_API_KEY", label: "OPENROUTER_API_KEY" },
|
|
{ key: "OPENAI_API_KEY", label: "OPENAI_API_KEY" },
|
|
{ key: "ANTHROPIC_API_KEY", label: "ANTHROPIC_API_KEY" },
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h1 className="text-2xl font-bold tracking-tight">AI Configuration</h1>
|
|
<p className="text-muted-foreground text-sm mt-1">
|
|
Current AI provider settings. Override values in{" "}
|
|
<Link href="/admin/settings" className="text-primary underline-offset-4 hover:underline">
|
|
Site Settings
|
|
</Link>
|
|
.
|
|
</p>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Active Provider</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center gap-3">
|
|
<Badge className="text-sm px-3 py-1">{active.provider}</Badge>
|
|
<span className="text-muted-foreground text-sm">{active.description}</span>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-3">
|
|
Priority: OpenRouter → OpenAI → Anthropic → Ollama (first configured key wins).
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">API Keys</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{keyRows.map(({ key, label }) => {
|
|
const meta = settings[key];
|
|
const present = !!meta?.value;
|
|
return (
|
|
<div key={key} className="flex items-center justify-between py-2 border-b last:border-0">
|
|
<div className="flex items-center gap-2">
|
|
{present ? (
|
|
<CheckCircle className="h-4 w-4 text-green-500" />
|
|
) : (
|
|
<XCircle className="h-4 w-4 text-muted-foreground" />
|
|
)}
|
|
<span className="font-mono text-sm">{label}</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{present && meta?.fromDb && (
|
|
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
<Database className="h-3 w-3" /> DB override
|
|
</span>
|
|
)}
|
|
{present && !meta?.fromDb && (
|
|
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
<Cpu className="h-3 w-3" /> from .env
|
|
</span>
|
|
)}
|
|
<Badge variant={present ? "default" : "secondary"}>
|
|
{present ? "Configured" : "Not set"}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
<div className="flex items-center justify-between py-2 border-b">
|
|
<span className="font-mono text-sm text-muted-foreground">OLLAMA_BASE_URL</span>
|
|
<span className="text-sm text-muted-foreground font-mono">
|
|
{settings["OLLAMA_BASE_URL"]?.value || "http://localhost:11434 (default)"}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center justify-between py-2">
|
|
<span className="font-mono text-sm text-muted-foreground">OPENROUTER_DEFAULT_MODEL</span>
|
|
<span className="text-sm text-muted-foreground font-mono">
|
|
{settings["OPENROUTER_DEFAULT_MODEL"]?.value || "google/gemini-flash-1.5 (default)"}
|
|
</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|