import { useState } from "react" import { fetchPoolKeys } from "@/lib/api" import { cn } from "@/lib/utils" import { Button } from "./ui/button" import { Input } from "./ui/input" const POOL_TOKEN_RE = /^__fcc_key_(\d+)__$/ const MASKED_SECRET = "********" interface PoolItem { token: string | null raw?: string } interface PoolEditorProps { value: string locked: boolean keyCount: number fieldKey: string disabled?: boolean onChange: (value: string) => void onMessage: (text: string, kind?: string) => void } function mask(raw: string): string { return "●".repeat(Math.min(24, Math.max(12, raw.length))) } function tokenIndex(token: string): number { const match = POOL_TOKEN_RE.exec(token) return match ? Number(match[1]) : -1 } export function PoolEditor({ value, locked, keyCount, fieldKey, disabled = false, onChange, onMessage, }: PoolEditorProps) { const [items, setItems] = useState(() => (value || "").split(",").filter(Boolean).map((part) => ({ token: part })), ) const [newKey, setNewKey] = useState("") const [rawKeys, setRawKeys] = useState(null) const [revealIndex, setRevealIndex] = useState(null) const sync = (next: PoolItem[]) => { setItems(next) onChange(next.map((item) => item.token || item.raw || "").join(",")) } const addKey = () => { const raw = newKey.trim() if (!raw) return if (raw.includes(",")) { onMessage("API keys cannot contain commas.", "error") return } if (POOL_TOKEN_RE.test(raw)) { onMessage("This key format is reserved; enter a real API key.", "error") return } sync([...items, { token: null, raw }]) setNewKey("") } const reveal = async (index: number) => { if (rawKeys === null) { try { const result = await fetchPoolKeys(fieldKey) setRawKeys(result.keys) } catch (error) { onMessage(error instanceof Error ? error.message : String(error), "error") return } } setRevealIndex((current) => (current === index ? null : index)) } const displayedValue = (item: PoolItem, index: number): string => { if (revealIndex !== index) return item.token ? MASKED_SECRET : mask(item.raw ?? "") if (item.token) { const rawIndex = tokenIndex(item.token) return rawKeys && rawIndex >= 0 && rawIndex < rawKeys.length ? rawKeys[rawIndex] : MASKED_SECRET } return item.raw ?? "" } const editValue = (index: number, next: string) => { sync( items.map((item, itemIndex) => { if (itemIndex !== index) return item if (item.token) return { token: null, raw: next } return { ...item, raw: next } }), ) } const hint = keyCount === 0 ? "Add one or more API keys. Multiple keys rotate round-robin with failover." : keyCount === 1 ? "One key configured. Add extra keys for round-robin and failover." : "Multiple keys are used in rotation. Add extra keys for round-robin and failover." return (
{items.map((item, index) => (
{revealIndex === index ? ( editValue(index, event.target.value)} onBlur={() => setRevealIndex(null)} className="h-8 flex-1 font-mono text-xs" /> ) : ( {displayedValue(item, index)} )}
))}
setNewKey(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault() addKey() } }} className={cn(locked && "opacity-50")} />

{hint}

) }