Overview

A settings panel that lets users connect multiple AI provider API keys. The first connected provider is used as the active model; subsequent providers are automatic fallbacks. Keys are stored encrypted in the database.

Origin: ~/claritysrc/components/settings/ai-providers-panel.tsx


When to Use

Add this panel to any JB Cloud Next.js app that needs AI provider management. Copy the component files directly from Clarity and adapt the PROVIDERS array for your app's supported models.


Files to Copy

File Purpose
src/components/settings/ai-providers-panel.tsx Main UI panel — full component
src/app/api/integrations/[provider]/route.ts API route — POST (save) / DELETE (remove)
src/lib/crypto.ts encryptToken() helper

The ai-connect-form.tsx file is an older, simpler version. Prefer ai-providers-panel.tsx for new work.

Logo Files

Logos are stored in the claude-codex assets directory (~/.claude/assets/logos/), synced to all machines via /codex-sync. Copy them to the target project:

mkdir -p public/logos
cp ~/.claude/assets/logos/claude-logo.svg  public/logos/
cp ~/.claude/assets/logos/google-logo.svg  public/logos/
cp ~/.claude/assets/logos/groq-logo.svg    public/logos/

DeepSeek has a logo (DeepSeek_idPu03Khfd_1.svg) but it is a wide wordmark (195×41 viewBox) — not suitable for an 8×8 avatar. Use the teal "D" initial instead.

Provider Logo file Notes
Claude (Anthropic) claude-logo.svg Coral swirl mark (#D97757), transparent bg
Gemini (Google) google-logo.svg Multicolor Google G, transparent bg
DeepSeek Teal initial "D"
Groq groq-logo.svg Red square bg (#F54F35) baked into SVG

Component: AIProvidersPanel

// src/components/settings/ai-providers-panel.tsx
type ProviderId = "anthropic" | "gemini" | "deepseek" | "groq"

interface ProviderConfig {
  id: ProviderId
  label: string
  model: string          // shown as monospace label
  description: string    // shown as muted subtitle
  placeholder: string    // API key format hint
  docsUrl: string        // link to provider's API key page
  avatarColor: string    // tailwind bg class — used when no logoSrc
  initial: string        // 1-2 chars for avatar fallback
  logoSrc?: string       // path to SVG in public/logos/; absent = colored initial
}

const PROVIDERS: ProviderConfig[] = [
  { id: "anthropic", label: "Claude",   model: "claude-sonnet-4-6",
    avatarColor: "bg-violet-500", initial: "A",  logoSrc: "/logos/claude-logo.svg" },
  { id: "gemini",    label: "Gemini",   model: "gemini-2.0-flash",
    avatarColor: "bg-blue-500",   initial: "G",  logoSrc: "/logos/google-logo.svg" },
  { id: "deepseek", label: "DeepSeek",  model: "deepseek-chat",
    avatarColor: "bg-teal-500",   initial: "D"   /* no logo */ },
  { id: "groq",     label: "Groq",     model: "llama-3.3-70b-versatile",
    avatarColor: "bg-orange-500", initial: "Gr", logoSrc: "/logos/groq-logo.svg" },
]

// Avatar render (replaces the colored-initial div):
// {provider.logoSrc ? (
//   <img src={provider.logoSrc} alt={provider.label}
//        className="w-8 h-8 rounded-lg object-contain shrink-0" />
// ) : (
//   <div className={cn("w-8 h-8 rounded-lg flex items-center justify-center",
//                      "text-white text-xs font-semibold shrink-0",
//                      provider.avatarColor)}>
//     {provider.initial}
//   </div>
// )}

interface Props {
  connected: Record<ProviderId, boolean>
}

UI Behavior