From 44906ee3d006eeca8114fa10fc43d6b88b7b6607 Mon Sep 17 00:00:00 2001 From: Krishav Raj Singh Date: Sun, 13 Jul 2025 11:50:37 +0530 Subject: [PATCH] frontend for local api key management --- backend/local_llm/api.py | 1 - backend/utils/local_api_keys.py | 13 +- .../(personalAccount)/settings/layout.tsx | 1 + .../settings/llm-api-keys/page.tsx | 9 ++ .../src/components/api-keys/llm-api-keys.tsx | 133 ++++++++++++++++++ .../sidebar/nav-user-with-teams.tsx | 8 ++ .../thread/chat-input/model-selector.tsx | 20 ++- 7 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 frontend/src/app/(dashboard)/(personalAccount)/settings/llm-api-keys/page.tsx create mode 100644 frontend/src/components/api-keys/llm-api-keys.tsx diff --git a/backend/local_llm/api.py b/backend/local_llm/api.py index 4f89baa0..507adff6 100644 --- a/backend/local_llm/api.py +++ b/backend/local_llm/api.py @@ -22,7 +22,6 @@ def save_local_llm_keys(request: Dict[str, str]) -> Dict[str, str]: if config.ENV_MODE != EnvMode.LOCAL: raise HTTPException(status_code=403, detail="API key management only available in local mode") - print(f"Saving local LLM keys: {request}") key_saved = save_local_api_keys(request) if key_saved: return {"message": "API keys saved successfully"} diff --git a/backend/utils/local_api_keys.py b/backend/utils/local_api_keys.py index 0379390d..d722393d 100644 --- a/backend/utils/local_api_keys.py +++ b/backend/utils/local_api_keys.py @@ -14,12 +14,9 @@ from utils.constants import PROVIDERS def get_local_api_keys(providers: List[str]) -> Dict[str, str]: """Get API keys from .env file in local mode.""" - if config.ENV_MODE != EnvMode.LOCAL: - return {} - try: # Load current env vars - load_dotenv() + load_dotenv(override=True) return {provider: os.getenv(provider) or "" for provider in providers} @@ -29,9 +26,6 @@ def get_local_api_keys(providers: List[str]) -> Dict[str, str]: def save_local_api_keys(api_keys: Dict[str, str]) -> bool: """Save API keys to .env file in local mode.""" - if config.ENV_MODE != EnvMode.LOCAL: - return False - try: # Find .env file env_path = find_dotenv() @@ -41,9 +35,8 @@ def save_local_api_keys(api_keys: Dict[str, str]) -> bool: # Update each API key for key, value in api_keys.items(): - if value: # Only set if value is not empty - set_key(env_path, key, value) - logger.info(f"Updated {key} in .env file") + set_key(env_path, key, value) + logger.info(f"Updated {key} in .env file") return True diff --git a/frontend/src/app/(dashboard)/(personalAccount)/settings/layout.tsx b/frontend/src/app/(dashboard)/(personalAccount)/settings/layout.tsx index 5f4b3d73..196f5869 100644 --- a/frontend/src/app/(dashboard)/(personalAccount)/settings/layout.tsx +++ b/frontend/src/app/(dashboard)/(personalAccount)/settings/layout.tsx @@ -15,6 +15,7 @@ export default function PersonalAccountSettingsPage({ // { name: "Teams", href: "/settings/teams" }, { name: 'Billing', href: '/settings/billing' }, { name: 'Usage Logs', href: '/settings/usage-logs' }, + { name: 'LLM API Keys', href: '/settings/llm-api-keys' }, ]; return ( <> diff --git a/frontend/src/app/(dashboard)/(personalAccount)/settings/llm-api-keys/page.tsx b/frontend/src/app/(dashboard)/(personalAccount)/settings/llm-api-keys/page.tsx new file mode 100644 index 00000000..93a53b9a --- /dev/null +++ b/frontend/src/app/(dashboard)/(personalAccount)/settings/llm-api-keys/page.tsx @@ -0,0 +1,9 @@ +import { isLocalMode } from "@/lib/config"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Shield } from "lucide-react"; +import { LLMApiKeys } from "@/components/api-keys/llm-api-keys"; + +export default function LLMKeysPage() { + + return +} \ No newline at end of file diff --git a/frontend/src/components/api-keys/llm-api-keys.tsx b/frontend/src/components/api-keys/llm-api-keys.tsx new file mode 100644 index 00000000..31b6eec2 --- /dev/null +++ b/frontend/src/components/api-keys/llm-api-keys.tsx @@ -0,0 +1,133 @@ +"use client"; + +import { Eye, EyeOff } from "lucide-react"; +import { Button } from "../ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"; +import { isLocalMode } from "@/lib/config"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { useEffect, useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { backendApi } from "@/lib/api-client"; +import { toast } from "sonner"; +import { useForm } from "react-hook-form"; + +interface APIKeyForm { + [key: string]: string; +} + +export function LLMApiKeys() { + const queryClient = useQueryClient(); + const [visibleKeys, setVisibleKeys] = useState>({}); + const {data: apiKeys, isLoading} = useQuery({ + queryKey: ['api-keys'], + queryFn: async() => { + const response = await backendApi.get('/local-llm-keys'); + return response.data; + }, + }); + + const { register, handleSubmit, formState: { errors, isDirty }, reset } = useForm({ + defaultValues: apiKeys || {} + }); + + const handleSave = async (data: APIKeyForm) => { + updateApiKeys.mutate(data); + } + const updateApiKeys = useMutation({ + mutationFn: async (data: APIKeyForm) => { + const response = await backendApi.post('/local-llm-keys', data); + await queryClient.invalidateQueries({ queryKey: ['api-keys'] }); + return response.data; + }, + onSuccess: (data) => { + toast.success(data.message); + }, + onError: () => { + toast.error('Failed to update API keys'); + } + }); + + const keysArray = apiKeys ? Object.entries(apiKeys).map(([key, value]) => ({ + id: key, + name: key.replace(/_/g, " ").replace("KEY", "Key"), + value: value + })) : []; + + useEffect(() => { + if (apiKeys) { + reset(apiKeys); + } + }, [apiKeys, reset]); + + const toggleKeyVisibility = (keyId: string) => { + setVisibleKeys(prev => ({ + ...prev, + [keyId]: !prev[keyId] + })); + } + + if (isLoading) { + return + + API Keys + Loading... + + ; + } + + return + + API Keys + + {isLocalMode() ? ( + <> + Manage your API keys for various Language Model providers. + + ) : ( + <> + API key management is only available in local mode. + + )} + + + + {isLocalMode() && ( + +
+ {keysArray && keysArray?.map((key: any) => ( + +
+ +
+ + +
+ {errors[key.id] &&

{errors[key.id]?.message}

} +
+ + ))} + +
+ +
+
+
+ )} +
+} \ No newline at end of file diff --git a/frontend/src/components/sidebar/nav-user-with-teams.tsx b/frontend/src/components/sidebar/nav-user-with-teams.tsx index ff02ff58..9b48983b 100644 --- a/frontend/src/components/sidebar/nav-user-with-teams.tsx +++ b/frontend/src/components/sidebar/nav-user-with-teams.tsx @@ -17,6 +17,7 @@ import { AudioWaveform, Sun, Moon, + Key, } from 'lucide-react'; import { useAccounts } from '@/hooks/use-accounts'; import NewTeamForm from '@/components/basejump/new-team-form'; @@ -48,6 +49,7 @@ import { } from '@/components/ui/dialog'; import { createClient } from '@/lib/supabase/client'; import { useTheme } from 'next-themes'; +import { isLocalMode } from '@/lib/config'; export function NavUserWithTeams({ user, @@ -286,6 +288,12 @@ export function NavUserWithTeams({ Billing + {isLocalMode() && + + + LLM API Keys + + } {/* diff --git a/frontend/src/components/thread/chat-input/model-selector.tsx b/frontend/src/components/thread/chat-input/model-selector.tsx index 1ad2b0c0..67b3181b 100644 --- a/frontend/src/components/thread/chat-input/model-selector.tsx +++ b/frontend/src/components/thread/chat-input/model-selector.tsx @@ -14,7 +14,7 @@ import { TooltipTrigger, } from '@/components/ui/tooltip'; import { Button } from '@/components/ui/button'; -import { Check, ChevronDown, Search, AlertTriangle, Crown, ArrowUpRight, Brain, Plus, Edit, Trash, Cpu } from 'lucide-react'; +import { Check, ChevronDown, Search, AlertTriangle, Crown, ArrowUpRight, Brain, Plus, Edit, Trash, Cpu, Key } from 'lucide-react'; import { ModelOption, SubscriptionStatus, @@ -32,6 +32,7 @@ import { cn } from '@/lib/utils'; import { useRouter } from 'next/navigation'; import { isLocalMode } from '@/lib/config'; import { CustomModelDialog, CustomModelFormData } from './custom-model-dialog'; +import Link from 'next/link'; interface CustomModel { id: string; @@ -674,6 +675,22 @@ export const ModelSelector: React.FC = ({
All Models {isLocalMode() && ( +
+ + + + + + + + + Manage API Keys + + + @@ -694,6 +711,7 @@ export const ModelSelector: React.FC = ({ +
)}
{uniqueModels