-
-
Notifications
You must be signed in to change notification settings - Fork 95
feat: onborading flow, add support for Anthropic Models, update settings ui #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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'; | ||||||||||||||||||||||||||||||||
|
|
@@ -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 { | ||||||||||||||||||||||||||||||||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update Anthropic models to match lib/models.ts. The Anthropic models list is incomplete - missing "Claude 3.7 Sonnet" which is defined in 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||
|
|
@@ -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> | ||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
Consider applying the same pattern to other cases for consistency:
📝 Committable suggestion
🧰 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