Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createGoogleGenerativeAI } from '@ai-sdk/google';
import { createOpenAI } from '@ai-sdk/openai';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { createAnthropic } from '@ai-sdk/anthropic';
import { streamText, smoothStream } from 'ai';
import { headers } from 'next/headers';
import { getModelConfig, AIModel } from '@/lib/models';
Expand Down Expand Up @@ -34,6 +35,11 @@ export async function POST(req: NextRequest) {
aiModel = openrouter(modelConfig.modelId);
break;

case 'anthropic':
const anthropic = createAnthropic({ apiKey });
aiModel = anthropic(modelConfig.modelId);
break;
Comment on lines +38 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix variable scoping issue in switch case.

The variable declaration should be wrapped in a block to prevent potential access from other switch cases.

Apply this diff to fix the scoping issue:

-      case 'anthropic':
-        const anthropic = createAnthropic({ apiKey });
-        aiModel = anthropic(modelConfig.modelId);
-        break;
+      case 'anthropic': {
+        const anthropic = createAnthropic({ apiKey });
+        aiModel = anthropic(modelConfig.modelId);
+        break;
+      }

Consider applying the same pattern to other cases for consistency:

-      case 'google':
-        const google = createGoogleGenerativeAI({ apiKey });
-        aiModel = google(modelConfig.modelId);
-        break;
+      case 'google': {
+        const google = createGoogleGenerativeAI({ apiKey });
+        aiModel = google(modelConfig.modelId);
+        break;
+      }

-      case 'openai':
-        const openai = createOpenAI({ apiKey });
-        aiModel = openai(modelConfig.modelId);
-        break;
+      case 'openai': {
+        const openai = createOpenAI({ apiKey });
+        aiModel = openai(modelConfig.modelId);
+        break;
+      }

-      case 'openrouter':
-        const openrouter = createOpenRouter({ apiKey });
-        aiModel = openrouter(modelConfig.modelId);
-        break;
+      case 'openrouter': {
+        const openrouter = createOpenRouter({ apiKey });
+        aiModel = openrouter(modelConfig.modelId);
+        break;
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case 'anthropic':
const anthropic = createAnthropic({ apiKey });
aiModel = anthropic(modelConfig.modelId);
break;
case 'anthropic': {
const anthropic = createAnthropic({ apiKey });
aiModel = anthropic(modelConfig.modelId);
break;
}
case 'google': {
const google = createGoogleGenerativeAI({ apiKey });
aiModel = google(modelConfig.modelId);
break;
}
case 'openai': {
const openai = createOpenAI({ apiKey });
aiModel = openai(modelConfig.modelId);
break;
}
case 'openrouter': {
const openrouter = createOpenRouter({ apiKey });
aiModel = openrouter(modelConfig.modelId);
break;
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 39-39: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🤖 Prompt for AI Agents
In app/api/chat/route.ts around lines 38 to 41, the variable 'anthropic' is
declared directly inside the switch case without a block, which can cause
scoping issues. To fix this, wrap the case body in curly braces to create a
block scope around the 'anthropic' declaration. Also, review other switch cases
and apply the same block scoping pattern for consistency and to avoid similar
issues.


default:
return new Response(
JSON.stringify({ error: 'Unsupported model provider' }),
Expand Down
195 changes: 122 additions & 73 deletions frontend/components/APIKeyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,7 @@ import { FieldError, useForm, UseFormRegister } from 'react-hook-form';

import { Button } from '@/frontend/components/ui/button';
import { Input } from '@/frontend/components/ui/input';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/frontend/components/ui/card';
import { Key } from 'lucide-react';
import { Key, ExternalLink, Shield } from 'lucide-react';
import { toast } from 'sonner';
import { useAPIKeyStore } from '@/frontend/stores/APIKeyStore';
import { Badge } from './ui/badge';
Expand All @@ -23,30 +16,50 @@ const formSchema = z.object({
}),
openrouter: z.string().trim().optional(),
openai: z.string().trim().optional(),
anthropic: z.string().trim().optional(),
});

type FormValues = z.infer<typeof formSchema>;

export default function APIKeyForm() {
interface APIKeyFormProps {
selectedProviders?: string[];
}

export default function APIKeyForm({ selectedProviders }: APIKeyFormProps = {}) {
return (
<Card className="w-full max-w-2xl mx-auto">
<CardHeader>
<div className="flex items-center gap-2">
<Key className="h-5 w-5" />
<CardTitle>Add Your API Keys To Start Chatting</CardTitle>
<div className="w-full max-w-2xl mx-auto space-y-6">
<div className="text-center space-y-4">
<div className="flex items-center justify-center gap-2">
<Key className="h-6 w-6 text-primary" />
<h2 className="text-2xl font-bold">Configure Your AI Providers</h2>
</div>
<p className="text-muted-foreground">
Add API keys for the AI models you want to use. Keys are stored securely in your browser and never sent to our servers.
</p>

<div className="bg-primary/5 border border-primary/20 rounded-lg p-4">
<div className="flex items-start gap-3">
<div className="p-1.5 bg-primary/10 rounded-full">
<Shield className="h-4 w-4 text-primary" />
</div>
<div className="flex-1 text-left">
<p className="text-sm font-medium">
Privacy First
</p>
<p className="text-xs text-muted-foreground mt-1">
Your API keys are stored locally and encrypted. We never have access to them.
</p>
</div>
</div>
</div>
<CardDescription>
Keys are stored locally in your browser.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<Form />
</CardContent>
</Card>
</div>

<Form selectedProviders={selectedProviders} />
</div>
);
}

const Form = () => {
const Form = ({ selectedProviders }: { selectedProviders?: string[] }) => {
const { keys, setKeys } = useAPIKeyStore();

const {
Expand All @@ -71,38 +84,62 @@ const Form = () => {
[setKeys]
);

const providerFields = [
{
id: 'google',
label: 'Google API Key',
models: ['Gemini 2.5 Flash', 'Gemini 2.5 Pro'],
linkUrl: 'https://aistudio.google.com/apikey',
placeholder: 'AIza...',
required: true,
},
{
id: 'openai',
label: 'OpenAI API Key',
models: ['GPT-4o', 'GPT-4.1-mini'],
linkUrl: 'https://platform.openai.com/settings/organization/api-keys',
placeholder: 'sk-...',
required: false,
},
{
id: 'anthropic',
label: 'Anthropic API Key',
models: ['Claude 3.5 Sonnet', 'Claude 3.5 Haiku'],
linkUrl: 'https://console.anthropic.com/settings/keys',
placeholder: 'sk-ant-...',
required: false,
},
Comment on lines +105 to +111
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update Anthropic models to match lib/models.ts.

The Anthropic models list is incomplete - missing "Claude 3.7 Sonnet" which is defined in lib/models.ts.

Apply this diff to include all Anthropic models:

  {
    id: 'anthropic',
    label: 'Anthropic API Key',
-   models: ['Claude 3.5 Sonnet', 'Claude 3.5 Haiku'],
+   models: ['Claude 3.5 Sonnet', 'Claude 3.5 Haiku', 'Claude 3.7 Sonnet'],
    linkUrl: 'https://console.anthropic.com/settings/keys',
    placeholder: 'sk-ant-...',
    required: false,
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
id: 'anthropic',
label: 'Anthropic API Key',
models: ['Claude 3.5 Sonnet', 'Claude 3.5 Haiku'],
linkUrl: 'https://console.anthropic.com/settings/keys',
placeholder: 'sk-ant-...',
required: false,
},
{
id: 'anthropic',
label: 'Anthropic API Key',
models: ['Claude 3.5 Sonnet', 'Claude 3.5 Haiku', 'Claude 3.7 Sonnet'],
linkUrl: 'https://console.anthropic.com/settings/keys',
placeholder: 'sk-ant-...',
required: false,
},
🤖 Prompt for AI Agents
In frontend/components/APIKeyForm.tsx around lines 105 to 111, the list of
Anthropic models is missing "Claude 3.7 Sonnet" as defined in lib/models.ts.
Update the models array for Anthropic to include "Claude 3.7 Sonnet" along with
the existing models to ensure consistency with lib/models.ts.

{
id: 'openrouter',
label: 'OpenRouter API Key',
models: ['Deepseek R1 0528', 'Deepseek V3'],
linkUrl: 'https://openrouter.ai/settings/keys',
placeholder: 'sk-or-...',
required: false,
},
];

const fieldsToShow = selectedProviders
? providerFields.filter(field => selectedProviders.includes(field.id))
: providerFields;

return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<ApiKeyField
id="google"
label="Google API Key"
models={['Gemini 2.5 Flash', 'Gemini 2.5 Pro']}
linkUrl="https://aistudio.google.com/apikey"
placeholder="AIza..."
register={register}
error={errors.google}
required
/>

<ApiKeyField
id="openrouter"
label="OpenRouter API Key"
models={['DeepSeek R1 0538', 'DeepSeek-V3']}
linkUrl="https://openrouter.ai/settings/keys"
placeholder="sk-or-..."
register={register}
error={errors.openrouter}
/>

<ApiKeyField
id="openai"
label="OpenAI API Key"
models={['GPT-4o', 'GPT-4.1-mini']}
linkUrl="https://platform.openai.com/settings/organization/api-keys"
placeholder="sk-..."
register={register}
error={errors.openai}
/>
<div className="space-y-4">
{fieldsToShow.map((field) => (
<ApiKeyField
key={field.id}
id={field.id}
label={field.label}
models={field.models}
linkUrl={field.linkUrl}
placeholder={field.placeholder}
register={register}
error={errors[field.id as keyof FormValues]}
required={field.required}
/>
))}
</div>

<Button type="submit" className="w-full" disabled={!isDirty}>
Save API Keys
Expand Down Expand Up @@ -132,37 +169,49 @@ const ApiKeyField = ({
required,
register,
}: ApiKeyFieldProps) => (
<div className="flex flex-col gap-2">
<label
htmlFor={id}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 flex gap-1"
>
<span>{label}</span>
{required && <span className="text-muted-foreground"> (Required)</span>}
</label>
<div className="flex gap-2">
<div className="space-y-3 p-4 border rounded-lg bg-background/50 hover:bg-background/80 transition-colors">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<label
htmlFor={id}
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{label}
</label>
{required && <Badge variant="destructive" className="text-xs px-2 py-0.5">Required</Badge>}
</div>
<a
href={linkUrl}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-primary hover:text-primary/80 inline-flex items-center gap-1 hover:underline transition-colors"
>
Get API Key
<ExternalLink className="w-3 h-3" />
</a>
</div>

<div className="flex flex-wrap gap-1.5">
{models.map((model) => (
<Badge key={model}>{model}</Badge>
<Badge key={model} variant="outline" className="text-xs font-normal">{model}</Badge>
))}
</div>

<Input
id={id}
type="password"
placeholder={placeholder}
{...register(id as keyof FormValues)}
className={error ? 'border-red-500' : ''}
className={`transition-colors ${error ? 'border-destructive focus-visible:ring-destructive' : ''}`}
/>

<a
href={linkUrl}
target="_blank"
className="text-sm text-blue-500 inline w-fit"
>
Create {label.split(' ')[0]} API Key
</a>

{error && (
<p className="text-[0.8rem] font-medium text-red-500">{error.message}</p>
<p className="text-sm font-medium text-destructive flex items-center gap-1.5">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
{error.message}
</p>
)}
</div>
);
Loading