"use client"; import { useState, useEffect, useCallback } from "react"; import { getAIProviderSettings } from "@/actions/integrations/ai-providers"; // Types type AIProvider = "openai" | "anthropic" | "google" | "xai" | "custom"; interface AISettings { provider: AIProvider; apiKey: string; orgId: string; model: string; customEndpoint: string; } interface TestResult { ok: boolean; message: string; } // Provider configuration const PROVIDERS = [ { id: "openai" as AIProvider, name: "OpenAI", color: "bg-emerald-500", placeholder: "sk-...", website: "openai.com", description: "GPT-4o, GPT-4 Turbo. Industry standard.", models: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-3.5-turbo"], hasOrgId: true, }, { id: "anthropic" as AIProvider, name: "Anthropic", color: "bg-amber-500", placeholder: "sk-ant-...", website: "anthropic.com", description: "Claude 3.5 Sonnet, Claude 3 Opus. Best for reasoning.", models: ["claude-3-5-sonnet-20241002", "claude-3-5-sonnet-20250611", "claude-3-opus-20240229", "claude-3-haiku-20240307"], hasOrgId: false, }, { id: "google" as AIProvider, name: "Google", color: "bg-blue-500", placeholder: "AIza...", website: "ai.google", description: "Gemini 2.0 Flash, 1.5 Pro. Fast, cost-effective.", models: ["gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash", "gemini-pro"], hasOrgId: false, }, { id: "xai" as AIProvider, name: "xAI", color: "bg-orange-500", placeholder: "xai-...", website: "x.ai", description: "Grok-2, Grok-1.5. Real-time data, humor.", models: ["grok-2", "grok-2-mini", "grok-1.5"], hasOrgId: false, }, { id: "custom" as AIProvider, name: "Custom", color: "bg-stone-500", placeholder: "sk-...", website: "", description: "Any OpenAI-compatible API endpoint.", models: ["gpt-4o-mini"], hasOrgId: false, }, ] as const; // Estimated costs per 1M tokens (for display only) const MODEL_COSTS: Record = { "gpt-4o": { input: 2.50, output: 10.00 }, "gpt-4o-mini": { input: 0.15, output: 0.60 }, "gpt-4-turbo": { input: 10.00, output: 30.00 }, "gpt-3.5-turbo": { input: 0.50, output: 1.50 }, "claude-3-5-sonnet-20241002": { input: 3.00, output: 15.00 }, "claude-3-5-sonnet-20250611": { input: 3.00, output: 15.00 }, "claude-3-opus-20240229": { input: 15.00, output: 75.00 }, "claude-3-haiku-20240307": { input: 0.80, output: 4.00 }, "gemini-2.0-flash": { input: 0.10, output: 0.40 }, "gemini-1.5-pro": { input: 1.25, output: 5.00 }, "gemini-1.5-flash": { input: 0.075, output: 0.30 }, }; // Icons const SparkleIcon = ({ className }: { className?: string }) => ( ); const RobotIcon = ({ className }: { className?: string }) => ( ); const BrainIcon = ({ className }: { className?: string }) => ( ); const CircleIcon = ({ className }: { className?: string }) => ( ); const TornadoIcon = ({ className }: { className?: string }) => ( ); const WrenchIcon = ({ className }: { className?: string }) => ( ); const EyeIcon = ({ className }: { className?: string }) => ( ); const EyeOffIcon = ({ className }: { className?: string }) => ( ); const CheckIcon = ({ className }: { className?: string }) => ( ); const AlertIcon = ({ className }: { className?: string }) => ( ); const ShieldIcon = ({ className }: { className?: string }) => ( ); const BotIcon = ({ className }: { className?: string }) => ( ); const PROVIDER_ICONS: Record = { openai: , anthropic: , google: , xai: , custom: , }; type Props = { brandId: string; }; export default function AdvancedAIPanel({ brandId }: Props) { // Settings state const [settings, setSettings] = useState({ provider: "openai", apiKey: "", orgId: "", model: "gpt-4o-mini", customEndpoint: "", }); // UI state const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [testing, setTesting] = useState(false); const [showApiKey, setShowApiKey] = useState(false); const [result, setResult] = useState(null); const [hasChanges, setHasChanges] = useState(false); // Load existing settings via the AI-provider server action. Server actions // are normal async functions on the client (no `fetch` to flag) and the // action's auth gate ensures only the brand admin can read these settings. useEffect(() => { let cancelled = false; void (async () => { try { const data = await getAIProviderSettings(brandId); if (cancelled) return; setSettings({ provider: (data.provider as AIProvider) ?? "openai", apiKey: data.apiKey ?? "", orgId: data.orgId ?? "", model: data.model ?? "gpt-4o-mini", customEndpoint: data.customEndpoint ?? "", }); } catch (err) { if (!cancelled) console.error("Failed to load AI settings:", err); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, [brandId]); // Track changes const handleChange = useCallback((field: keyof AISettings, value: string) => { setSettings((prev) => ({ ...prev, [field]: value })); setHasChanges(true); setResult(null); }, []); // Select provider const handleSelectProvider = useCallback((provider: AIProvider) => { const providerConfig = PROVIDERS.find((p) => p.id === provider); const defaultModel = providerConfig?.models[0] ?? "gpt-4o-mini"; setSettings((prev) => ({ ...prev, provider, model: defaultModel, })); setHasChanges(true); setResult(null); }, []); // Save settings const handleSave = useCallback(async () => { setSaving(true); setResult(null); try { const res = await fetch("/api/integrations/ai-provider", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ brandId, ...settings, }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error ?? "Failed to save"); setResult({ ok: true, message: "AI provider settings saved successfully" }); setHasChanges(false); } catch (err) { setResult({ ok: false, message: err instanceof Error ? err.message : "Save failed" }); } finally { setSaving(false); } }, [brandId, settings]); // Test connection const handleTest = useCallback(async () => { if (!settings.apiKey?.trim()) { setResult({ ok: false, message: "API key is required to test connection" }); return; } setTesting(true); setResult(null); try { const res = await fetch("/api/integrations/ai-provider/test", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ brandId }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error ?? "Connection failed"); setResult({ ok: true, message: data.message ?? "Connection successful" }); } catch (err) { setResult({ ok: false, message: err instanceof Error ? err.message : "Test failed" }); } finally { setTesting(false); } }, [brandId, settings.apiKey]); // Get current provider config const currentProvider = PROVIDERS.find((p) => p.id === settings.provider) ?? PROVIDERS[0]; const modelCosts = MODEL_COSTS[settings.model]; // Loading skeleton if (loading) { return (
{[1, 2, 3, 4, 5].map((i) => (
))}
); } return (
{/* Security Warning Banner */}

API Key Security

  • Never share keys — Don't paste in Slack, email, or screenshots.
  • Usage costs add up fast — AI APIs charge per token. A busy month can hit $50-$200+.
  • Set budgets now — Use provider dashboard limits. Start with $10-$25/mo cap.
{/* Provider Selection Card */}

AI Provider

Choose your AI model provider for all AI tools

{PROVIDERS.map((provider) => ( ))}
{/* Credentials Card */} {settings.provider !== "custom" ? (

Credentials

{currentProvider.website}
{/* API Key */}
handleChange("apiKey", e.target.value)} placeholder={currentProvider.placeholder} className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500" />

Get your API key from {currentProvider.website}

{/* Organization ID (OpenAI only) */} {currentProvider.hasOrgId && (
handleChange("orgId", e.target.value)} placeholder="org-..." className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500" />
)}
) : ( /* Custom Endpoint Card */

Custom API Endpoint

Connect any OpenAI-compatible API

handleChange("apiKey", e.target.value)} placeholder="sk-..." className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500" />
handleChange("customEndpoint", e.target.value)} placeholder="https://api.openai.com/v1 or http://localhost:11434/v1" className="w-full rounded-lg border border-[var(--admin-border)] bg-white px-3 py-2.5 text-sm text-[var(--admin-text-primary)] placeholder:text-stone-400 outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500" />

Examples: OpenAI proxy, Ollama (localhost:11434), LM Studio

)} {/* Model Selection Card */}

Model

{modelCosts && (
Est. cost per 1M tokens:
In: ${modelCosts.input} Out: ${modelCosts.output}
)}
{currentProvider.models.map((model) => ( ))}
{/* AI Features Card */}

AI Features

{[ { name: "Campaign Writer", desc: "Generate email content", icon: }, { name: "Product Writer", desc: "Product descriptions", icon: }, { name: "Report Explainer", desc: "AI summaries", icon: }, { name: "Pricing Advisor", desc: "Smart pricing", icon: }, ].map((feature) => (
{feature.icon}

{feature.name}

{feature.desc}

))}
{/* Result Message */} {result && (
{result.ok ? : } {result.message}
)} {/* Action Buttons */}
); }