Arrakis / src /components /SettingsPanel.tsx
Trxon's picture
Added changes
8f67306
Raw
History Blame Contribute Delete
7.96 kB
import { useState } from "react";
import { motion } from "framer-motion";
import {
Settings,
Server,
Key,
Sparkles,
ChevronRight,
ExternalLink,
Eye,
EyeOff,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { ScrollArea } from "@/components/ui/scroll-area";
import { AppSettings, AIProvider } from "@/types/chat";
import { cn } from "@/lib/utils";
interface SettingsPanelProps {
settings: AppSettings;
onUpdate: (settings: Partial<AppSettings>) => void;
}
const providers: { id: AIProvider; name: string; description: string; icon: React.ReactNode }[] = [
{
id: "ollama",
name: "Ollama",
description: "Run AI models locally on your machine",
icon: <Server className="h-5 w-5" />,
},
{
id: "openai",
name: "OpenAI",
description: "Use GPT-4, GPT-4o and other OpenAI models",
icon: <Sparkles className="h-5 w-5" />,
},
];
export function SettingsPanel({ settings, onUpdate }: SettingsPanelProps) {
const [showApiKey, setShowApiKey] = useState(false);
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="p-4 border-b border-border">
<h2 className="text-lg font-semibold flex items-center gap-2">
<Settings className="h-5 w-5 text-primary" />
Settings
</h2>
</div>
<ScrollArea className="flex-1">
<div className="p-4 space-y-6">
{/* Provider Selection */}
<div>
<Label className="text-sm font-medium mb-3 block">AI Provider</Label>
<div className="space-y-2">
{providers.map((provider) => (
<motion.button
key={provider.id}
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
onClick={() => onUpdate({ provider: provider.id })}
className={cn(
"w-full p-3 rounded-xl text-left transition-all glass-card",
settings.provider === provider.id
? "border-primary/50 bg-primary/5"
: "hover:bg-accent/50"
)}
>
<div className="flex items-center gap-3">
<div
className={cn(
"p-2 rounded-lg",
settings.provider === provider.id
? "bg-primary/20 text-primary"
: "bg-muted text-muted-foreground"
)}
>
{provider.icon}
</div>
<div className="flex-1">
<div className="flex items-center justify-between">
<span className="font-medium">{provider.name}</span>
{settings.provider === provider.id && (
<span className="text-xs px-2 py-0.5 rounded-full bg-primary/20 text-primary">
Active
</span>
)}
</div>
<p className="text-xs text-muted-foreground mt-0.5">
{provider.description}
</p>
</div>
</div>
</motion.button>
))}
</div>
</div>
{/* Ollama Settings */}
{settings.provider === "ollama" && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
className="space-y-4"
>
<div>
<Label htmlFor="ollama-url" className="text-sm">
Ollama Server URL
</Label>
<Input
id="ollama-url"
value={settings.ollamaUrl}
onChange={(e) => onUpdate({ ollamaUrl: e.target.value })}
placeholder="http://localhost:11434"
className="mt-1.5 bg-input/50"
/>
<p className="text-xs text-muted-foreground mt-1">
Make sure Ollama is running with CORS enabled
</p>
</div>
<div>
<Label htmlFor="ollama-model" className="text-sm">
Default Model
</Label>
<Input
id="ollama-model"
value={settings.ollamaModel}
onChange={(e) => onUpdate({ ollamaModel: e.target.value })}
placeholder="llama3.2"
className="mt-1.5 bg-input/50"
/>
</div>
</motion.div>
)}
{/* OpenAI Settings */}
{settings.provider === "openai" && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
className="space-y-4"
>
<div>
<Label htmlFor="openai-key" className="text-sm">
API Key
</Label>
<div className="relative mt-1.5">
<Input
id="openai-key"
type={showApiKey ? "text" : "password"}
value={settings.openaiApiKey}
onChange={(e) => onUpdate({ openaiApiKey: e.target.value })}
placeholder="sk-..."
className="pr-10 bg-input/50"
/>
<button
type="button"
onClick={() => setShowApiKey(!showApiKey)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
{showApiKey ? (
<EyeOff className="h-4 w-4" />
) : (
<Eye className="h-4 w-4" />
)}
</button>
</div>
<p className="text-xs text-muted-foreground mt-1">
Your API key is stored locally in your browser
</p>
</div>
<div>
<Label htmlFor="openai-model" className="text-sm">
Model
</Label>
<Input
id="openai-model"
value={settings.openaiModel}
onChange={(e) => onUpdate({ openaiModel: e.target.value })}
placeholder="gpt-4o-mini"
className="mt-1.5 bg-input/50"
/>
</div>
</motion.div>
)}
{/* Help */}
<div className="pt-4 border-t border-border">
<p className="text-xs text-muted-foreground mb-3">Need help?</p>
<div className="space-y-2">
<a
href="https://ollama.com/download"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-sm text-primary hover:underline"
>
<ExternalLink className="h-3.5 w-3.5" />
Download Ollama
</a>
<a
href="https://platform.openai.com/api-keys"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-sm text-primary hover:underline"
>
<ExternalLink className="h-3.5 w-3.5" />
Get OpenAI API Key
</a>
</div>
</div>
</div>
</ScrollArea>
</div>
);
}