UNPKG

byokay-kit

Version:

Byokay Kit lets users bring their own AI API keys and store them securely in their browser. This eliminates the need for your app to manage sensitive credentials or maintain AI API backend infrastructure.

117 lines (116 loc) 8.98 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { useState, useEffect } from "react"; import { KeyManager } from "../core/KeyManager"; const manager = new KeyManager(); // Provider display names based on actual SupportedProvider type const providerNames = { openai: "OpenAI", claude: "Anthropic Claude", gemini: "Google Gemini", }; export function KeyInput({ providers = ["openai"], className }) { const [keys, setKeys] = useState({}); const [saved, setSaved] = useState({}); const [validating, setValidating] = useState({}); const [validated, setValidated] = useState({}); const [showInput, setShowInput] = useState(false); const hasAnyKey = providers.some((provider) => Boolean(keys[provider])); useEffect(() => { const storedKeys = {}; providers.forEach((provider) => { const stored = manager.getKey(provider); if (stored) { storedKeys[provider] = stored; setValidated((prev) => ({ ...prev, [provider]: true, })); } }); setKeys(storedKeys); }, [providers]); const handleSave = (provider, key) => { if (!key.trim()) return; manager.setKey(provider, key); setKeys((prev) => ({ ...prev, [provider]: key, })); setSaved((prev) => ({ ...prev, [provider]: true, })); setTimeout(() => { setSaved((prev) => ({ ...prev, [provider]: false, })); }, 1500); }; const handleClear = (provider) => { manager.removeKey(provider); setKeys((prev) => { const newKeys = { ...prev }; delete newKeys[provider]; return newKeys; }); setValidated((prev) => ({ ...prev, [provider]: false, })); }; // Simulates validating the API key const handleValidate = (provider, key) => { if (!key.trim()) return; setValidating((prev) => ({ ...prev, [provider]: true, })); // Simulate validation process setTimeout(() => { // Here we would actually validate with the API // For now we just simulate success handleSave(provider, key); setValidated((prev) => ({ ...prev, [provider]: true, })); setValidating((prev) => ({ ...prev, [provider]: false, })); }, 1000); }; // Save all changes and close the dialog const handleSaveAll = () => { // Any keys that have been entered but not explicitly saved should be saved Object.entries(keys).forEach(([provider, key]) => { if (key && !validated[provider]) { handleSave(provider, key); } }); // Close the dialog setShowInput(false); }; return (_jsx("div", { className: className, children: !showInput ? (_jsx("button", { onClick: () => setShowInput(true), className: "flex items-center gap-2 text-blue-600 bg-blue-50 hover:bg-blue-100 px-3 py-2 rounded-full text-sm font-medium transition-colors", children: hasAnyKey ? "Connected" : "Connect AI" })) : (_jsx("div", { className: "fixed inset-0 flex items-center justify-center bg-black/50 z-50", children: _jsxs("div", { className: "bg-white shadow-lg rounded-xl overflow-hidden border border-gray-200 animate-fade-in transition-all w-full max-w-md", children: [_jsxs("div", { className: "bg-blue-50 px-4 py-3 flex justify-between items-center border-b border-gray-200", children: [_jsx("h3", { className: "font-medium text-gray-800", children: "Connect AI Providers" }), _jsx("button", { onClick: () => setShowInput(false), className: "text-gray-500 hover:text-gray-700 rounded-full p-1 hover:bg-gray-100 transition-colors", "aria-label": "Close", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-5 w-5", viewBox: "0 0 20 20", fill: "currentColor", children: _jsx("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }) }) })] }), _jsxs("div", { className: "p-4 max-h-[80vh] overflow-y-auto", children: [_jsxs("div", { className: "w-full", children: [_jsxs("div", { className: "grid grid-cols-12 gap-2 mb-2 text-xs text-gray-500 font-medium px-1", children: [_jsx("div", { className: "col-span-3", children: "Provider" }), _jsx("div", { className: "col-span-7", children: "API Key" }), _jsx("div", { className: "col-span-2 text-center", children: "Actions" })] }), providers.map((provider) => (_jsxs("div", { className: "grid grid-cols-12 gap-2 items-center mb-3 py-2 border-b border-gray-100", children: [_jsx("div", { className: "col-span-3 flex items-center", children: _jsxs("div", { className: `text-sm font-medium ${keys[provider] && validated[provider] ? "text-green-700" : "text-gray-700"}`, children: [providerNames[provider], keys[provider] && validated[provider] && (_jsx("span", { className: "ml-1 inline-block w-2 h-2 bg-green-500 rounded-full" }))] }) }), _jsx("div", { className: "col-span-7 flex", children: _jsx("input", { type: "password", value: keys[provider] || "", onChange: (e) => { setKeys((prev) => ({ ...prev, [provider]: e.target.value, })); // Reset validation state when input changes if (validated[provider]) { setValidated((prev) => ({ ...prev, [provider]: false, })); } }, placeholder: "Enter API key", className: "w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-blue-500 outline-none" }) }), _jsx("div", { className: "col-span-2 flex items-center justify-end space-x-1", children: saved[provider] ? (_jsx("span", { className: "text-green-600 text-xs bg-green-50 py-1 px-2 rounded-full", children: "Saved!" })) : validating[provider] ? (_jsx("span", { className: "text-blue-600 text-xs bg-blue-50 py-1 px-2 rounded-full animate-pulse", children: "Validating..." })) : (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => handleValidate(provider, keys[provider] || ""), disabled: !keys[provider], className: `p-1.5 rounded ${validated[provider] ? "text-green-600 bg-green-50" : keys[provider] ? "text-gray-400 hover:text-green-600 hover:bg-green-50" : "text-gray-300 cursor-not-allowed"}`, title: validated[provider] ? "Validated" : "Validate key", children: _jsx("span", { className: `text-lg ${validated[provider] ? "font-bold" : "opacity-60"}`, children: "\u2713" }) }), keys[provider] && (_jsx("button", { onClick: () => handleClear(provider), className: "p-1.5 rounded text-red-600 hover:text-red-800 bg-red-50 hover:bg-red-100", title: "Remove key", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) }) }))] })) })] }, provider)))] }), _jsxs("div", { className: "mt-4 pt-3 border-t border-gray-100 flex justify-end space-x-3", children: [_jsx("button", { onClick: () => setShowInput(false), className: "px-4 py-2 bg-gray-100 text-gray-700 text-sm font-medium rounded-lg hover:bg-gray-200 transition-colors", children: "Cancel" }), _jsx("button", { onClick: handleSaveAll, className: "px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors", children: "Save" })] })] })] }) })) })); }