From ac5d278a9b6d84b72fc26a6dfe0d799274b795d5 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Wed, 30 Jul 2025 23:30:39 +0545 Subject: [PATCH] feat(nextjs): adds claude support in home and settings page --- servers/nextjs/app/api/user-config/route.ts | 6 + servers/nextjs/components/AnthropicConfig.tsx | 230 ++++++++++++++++++ servers/nextjs/components/GoogleConfig.tsx | 2 +- servers/nextjs/components/LLMSelection.tsx | 26 +- servers/nextjs/components/OllamaConfig.tsx | 4 +- servers/nextjs/components/OpenAIConfig.tsx | 2 +- servers/nextjs/types/global.d.ts | 7 + servers/nextjs/utils/providerConstants.ts | 5 + servers/nextjs/utils/providerUtils.ts | 8 +- servers/nextjs/utils/storeHelpers.ts | 20 +- 10 files changed, 295 insertions(+), 15 deletions(-) create mode 100644 servers/nextjs/components/AnthropicConfig.tsx diff --git a/servers/nextjs/app/api/user-config/route.ts b/servers/nextjs/app/api/user-config/route.ts index 39ba481e..a061326b 100644 --- a/servers/nextjs/app/api/user-config/route.ts +++ b/servers/nextjs/app/api/user-config/route.ts @@ -36,6 +36,8 @@ export async function POST(request: Request) { LLM: userConfig.LLM || existingConfig.LLM, OPENAI_API_KEY: userConfig.OPENAI_API_KEY || existingConfig.OPENAI_API_KEY, GOOGLE_API_KEY: userConfig.GOOGLE_API_KEY || existingConfig.GOOGLE_API_KEY, + ANTHROPIC_API_KEY: userConfig.ANTHROPIC_API_KEY || existingConfig.ANTHROPIC_API_KEY, + ANTHROPIC_MODEL: userConfig.ANTHROPIC_MODEL || existingConfig.ANTHROPIC_MODEL, OLLAMA_URL: userConfig.OLLAMA_URL || existingConfig.OLLAMA_URL, OLLAMA_MODEL: userConfig.OLLAMA_MODEL || existingConfig.OLLAMA_MODEL, CUSTOM_LLM_URL: userConfig.CUSTOM_LLM_URL || existingConfig.CUSTOM_LLM_URL, @@ -50,6 +52,10 @@ export async function POST(request: Request) { userConfig.USE_CUSTOM_URL === undefined ? existingConfig.USE_CUSTOM_URL : userConfig.USE_CUSTOM_URL, + EXTENDED_REASONING: + userConfig.EXTENDED_REASONING === undefined + ? existingConfig.EXTENDED_REASONING + : userConfig.EXTENDED_REASONING, }; fs.writeFileSync(userConfigPath, JSON.stringify(mergedConfig)); return NextResponse.json(mergedConfig); diff --git a/servers/nextjs/components/AnthropicConfig.tsx b/servers/nextjs/components/AnthropicConfig.tsx new file mode 100644 index 00000000..47aa340c --- /dev/null +++ b/servers/nextjs/components/AnthropicConfig.tsx @@ -0,0 +1,230 @@ +"use client"; +import { useEffect, useState } from "react"; +import { Check, ChevronsUpDown, Loader2 } from "lucide-react"; +import { Button } from "./ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "./ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; +import { cn } from "@/lib/utils"; +import { toast } from "sonner"; +import { Switch } from "./ui/switch"; + +interface AnthropicConfigProps { + anthropicApiKey: string; + anthropicModel: string; + extendedReasoning: boolean; + onInputChange: (value: string | boolean, field: string) => void; +} + + +export default function AnthropicConfig({ + anthropicApiKey, + anthropicModel, + extendedReasoning, + onInputChange, +}: AnthropicConfigProps) { + const [openModelSelect, setOpenModelSelect] = useState(false); + const [availableModels, setAvailableModels] = useState([]); + const [modelsLoading, setModelsLoading] = useState(false); + const [modelsChecked, setModelsChecked] = useState(false); + const [apiKey, setApiKey] = useState(anthropicApiKey); + + useEffect(() => { + setAvailableModels([]); + setModelsChecked(false); + onInputChange("", "anthropic_model"); + }, [apiKey]); + + const onApiKeyChange = (value: string) => { + setApiKey(value); + onInputChange(value, "anthropic_api_key"); + }; + + const fetchAvailableModels = async () => { + if (!anthropicApiKey) return; + + setModelsLoading(true); + try { + const response = await fetch('/api/v1/ppt/anthropic/models/available', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + api_key: anthropicApiKey + }), + }); + + if (response.ok) { + const data = await response.json(); + setAvailableModels(data); + setModelsChecked(true); + } else { + console.error('Failed to fetch models'); + setAvailableModels([]); + setModelsChecked(true); + } + } catch (error) { + console.error('Error fetching models:', error); + toast.error('Error fetching models'); + setAvailableModels([]); + setModelsChecked(true); + } finally { + setModelsLoading(false); + } + }; + + return ( +
+ {/* API Key Input */} +
+ +
+ onApiKeyChange(e.target.value)} + className="w-full px-4 py-2.5 outline-none border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-colors" + placeholder="Enter your Anthropic API key" + /> +
+

+ + Your API key will be stored locally and never shared +

+
+ + {/* Extended Reasoning Toggle */} + {/*
+
+ + onInputChange(checked, "extended_reasoning")} + /> +
+

+ + Enable extended reasoning for more detailed and thorough responses +

+
*/} + + {/* Check for available models button - show when no models checked or no models found */} + {(!modelsChecked || (modelsChecked && availableModels.length === 0)) && ( +
+ +
+ )} + + {/* Show message if no models found */} + {modelsChecked && availableModels.length === 0 && ( +
+

+ No models found. Please make sure your API key is valid and has access to Anthropic models. +

+
+ )} + + {/* Model Selection - only show if models are available */} + {modelsChecked && availableModels.length > 0 ? ( +
+ +
+ + + + + + + + + No model found. + + {availableModels.map((model, index) => ( + { + onInputChange(value, "anthropic_model"); + setOpenModelSelect(false); + }} + > + +
+
+
+ + {model} + +
+
+
+
+ ))} +
+
+
+
+
+
+
+ ) : null} +
+ ); +} \ No newline at end of file diff --git a/servers/nextjs/components/GoogleConfig.tsx b/servers/nextjs/components/GoogleConfig.tsx index d595fbd4..2fe892e9 100644 --- a/servers/nextjs/components/GoogleConfig.tsx +++ b/servers/nextjs/components/GoogleConfig.tsx @@ -5,7 +5,7 @@ interface GoogleConfigProps { export default function GoogleConfig({ googleApiKey, onInputChange }: GoogleConfigProps) { return ( -
+
diff --git a/servers/nextjs/components/LLMSelection.tsx b/servers/nextjs/components/LLMSelection.tsx index 670d1c00..0cf2ff15 100644 --- a/servers/nextjs/components/LLMSelection.tsx +++ b/servers/nextjs/components/LLMSelection.tsx @@ -15,6 +15,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; import { cn } from "@/lib/utils"; import OpenAIConfig from "./OpenAIConfig"; import GoogleConfig from "./GoogleConfig"; +import AnthropicConfig from "./AnthropicConfig"; import OllamaConfig from "./OllamaConfig"; import CustomConfig from "./CustomConfig"; import { @@ -69,11 +70,13 @@ export default function LLMProviderSelection({ useEffect(() => { const needsModelSelection = (llmConfig.LLM === "ollama" && !llmConfig.OLLAMA_MODEL) || - (llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL); + (llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL) || + (llmConfig.LLM === "anthropic" && !llmConfig.ANTHROPIC_MODEL); const needsApiKey = ((llmConfig.IMAGE_PROVIDER === "dall-e-3" || llmConfig.LLM === "openai") && !llmConfig.OPENAI_API_KEY) || ((llmConfig.IMAGE_PROVIDER === "gemini_flash" || llmConfig.LLM === "google") && !llmConfig.GOOGLE_API_KEY) || + (llmConfig.LLM === "anthropic" && !llmConfig.ANTHROPIC_API_KEY) || (llmConfig.IMAGE_PROVIDER === "pexels" && !llmConfig.PEXELS_API_KEY) || (llmConfig.IMAGE_PROVIDER === "pixabay" && !llmConfig.PIXABAY_API_KEY); @@ -86,7 +89,7 @@ export default function LLMProviderSelection({ }, [llmConfig]); - const input_field_changed = (new_value: string, field: string) => { + const input_field_changed = (new_value: string | boolean, field: string) => { const updatedConfig = updateLLMConfig(llmConfig, field, new_value); setLlmConfig(updatedConfig); }; @@ -177,9 +180,10 @@ export default function LLMProviderSelection({ onValueChange={handleProviderChange} className="w-full" > - + OpenAI Google + Anthropic Ollama Custom @@ -210,6 +214,16 @@ export default function LLMProviderSelection({ /> + {/* Anthropic Content */} + + + + {/* Ollama Content */} {/* Image Provider Selection */} -
+
@@ -388,7 +402,9 @@ export default function LLMProviderSelection({ ? llmConfig.OLLAMA_MODEL ?? "xxxxx" : llmConfig.LLM === "custom" ? llmConfig.CUSTOM_MODEL ?? "xxxxx" - : LLM_PROVIDERS[llmConfig.LLM!]?.model_label || "xxxxx"}{" "} + : llmConfig.LLM === "anthropic" + ? llmConfig.ANTHROPIC_MODEL ?? "xxxxx" + : LLM_PROVIDERS[llmConfig.LLM!]?.model_label || "xxxxx"}{" "} for text generation and{" "} {llmConfig.IMAGE_PROVIDER && IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER] diff --git a/servers/nextjs/components/OllamaConfig.tsx b/servers/nextjs/components/OllamaConfig.tsx index f0f09b76..99d5c926 100644 --- a/servers/nextjs/components/OllamaConfig.tsx +++ b/servers/nextjs/components/OllamaConfig.tsx @@ -49,7 +49,7 @@ export default function OllamaConfig({ }: OllamaConfigProps) { return ( <> -
+
@@ -185,7 +185,7 @@ export default function OllamaConfig({

)}
-
+