wu981526092's picture
Simplify login page and remove API key option
bfea55d
import React, { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Card, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Settings,
Palette,
Monitor,
Sun,
Moon,
Smartphone,
Check,
GitBranch,
Layers,
Brain,
Zap,
Clock,
DollarSign,
} from "lucide-react";
import { useKGDisplayMode } from "@/context/KGDisplayModeContext";
import { useModelPreferences } from "@/hooks/useModelPreferences";
import { AVAILABLE_MODELS, ModelConfig } from "@/lib/models";
interface SettingsModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
type Theme = "light" | "dark" | "system";
export function SettingsModal({ open, onOpenChange }: SettingsModalProps) {
const [activeSection, setActiveSection] = useState<string>("appearance");
const [currentTheme, setCurrentTheme] = useState<Theme>("system");
const { mode: kgDisplayMode, setMode: setKGDisplayMode } = useKGDisplayMode();
const {
selectedModel,
currentModelConfig,
updateModelPreference,
isLoading,
} = useModelPreferences();
const handleThemeChange = (theme: Theme) => {
setCurrentTheme(theme);
// Apply theme to document
const root = document.documentElement;
if (theme === "dark") {
root.classList.add("dark");
} else if (theme === "light") {
root.classList.remove("dark");
} else {
// System theme - detect user preference
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
if (prefersDark) {
root.classList.add("dark");
} else {
root.classList.remove("dark");
}
}
// Save to localStorage
localStorage.setItem("theme", theme);
};
React.useEffect(() => {
// Load saved theme on mount
const savedTheme = localStorage.getItem("theme") as Theme;
if (savedTheme) {
setCurrentTheme(savedTheme);
handleThemeChange(savedTheme);
}
}, []);
const themeOptions = [
{
id: "light" as Theme,
name: "Light",
description: "Light mode for bright environments",
icon: Sun,
},
{
id: "dark" as Theme,
name: "Dark",
description: "Dark mode for low-light environments",
icon: Moon,
},
{
id: "system" as Theme,
name: "System",
description: "Follows your system preference",
icon: Monitor,
},
];
const sidebarSections = [
{
id: "appearance",
name: "Appearance",
icon: Palette,
},
{
id: "models",
name: "Models",
icon: Brain,
},
{
id: "general",
name: "General",
icon: Settings,
},
];
const getCostLevelColor = (costLevel: string) => {
switch (costLevel) {
case "low":
return "text-green-600";
case "medium":
return "text-yellow-600";
case "high":
return "text-red-600";
default:
return "text-gray-600";
}
};
const getSpeedIcon = (speed: string) => {
switch (speed) {
case "fast":
return Zap;
case "medium":
return Clock;
case "slow":
return Brain;
default:
return Clock;
}
};
const renderModelCard = (model: ModelConfig) => {
const SpeedIcon = getSpeedIcon(model.speed);
const isSelected = selectedModel === model.id;
return (
<Card
key={model.id}
className={`cursor-pointer transition-all duration-200 hover:shadow-md ${
isSelected
? "ring-2 ring-primary border-primary bg-primary/5"
: "border-border hover:border-primary/50"
}`}
onClick={() => updateModelPreference(model.id)}
>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-muted">
<Brain className="h-4 w-4" />
</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<h4 className="font-medium">{model.name}</h4>
{model.recommended && (
<Badge variant="secondary" className="text-xs">
Recommended
</Badge>
)}
</div>
<p className="text-sm text-muted-foreground mt-1">
{model.description}
</p>
<div className="flex items-center gap-4 mt-2">
<div className="flex items-center gap-1">
<SpeedIcon className="h-3 w-3 text-muted-foreground" />
<span className="text-xs text-muted-foreground capitalize">
{model.speed}
</span>
</div>
<div className="flex items-center gap-1">
<DollarSign
className={`h-3 w-3 ${getCostLevelColor(
model.costLevel
)}`}
/>
<span
className={`text-xs capitalize ${getCostLevelColor(
model.costLevel
)}`}
>
{model.costLevel} cost
</span>
</div>
<span className="text-xs text-muted-foreground">
{model.contextWindow} context
</span>
</div>
</div>
</div>
{isSelected && (
<div className="p-1 rounded-full bg-primary">
<Check className="h-3 w-3 text-primary-foreground" />
</div>
)}
</div>
</CardContent>
</Card>
);
};
const renderModelsSection = () => (
<div className="space-y-6">
{isLoading ? (
<div className="flex items-center justify-center p-8">
<div className="w-6 h-6 border-2 border-primary border-t-transparent rounded-full animate-spin mr-3" />
Loading model preferences...
</div>
) : (
<>
{/* Current Model Indicator */}
{currentModelConfig && (
<div className="bg-primary/10 border border-primary/20 p-4 rounded-lg">
<div className="flex items-center gap-3 mb-2">
<div className="p-2 rounded-lg bg-primary/20">
<Check className="h-4 w-4 text-primary" />
</div>
<div>
<h4 className="font-medium text-primary">
Currently Selected
</h4>
<p className="text-sm text-muted-foreground">
Active for all new graph generations
</p>
</div>
</div>
<div className="ml-11">
<p className="font-medium">{currentModelConfig.name}</p>
<p className="text-sm text-muted-foreground">
{currentModelConfig.description}
</p>
</div>
</div>
)}
<div>
<h3 className="text-lg font-semibold mb-4">Standard Models</h3>
<div className="space-y-3">
{AVAILABLE_MODELS.standard.map(renderModelCard)}
</div>
</div>
<Separator />
<div>
<h3 className="text-lg font-semibold mb-4">Reasoning Models</h3>
<p className="text-sm text-muted-foreground mb-4">
Specialized models for complex analysis and multi-step reasoning
tasks.
</p>
<div className="space-y-3">
{AVAILABLE_MODELS.reasoning.map(renderModelCard)}
</div>
</div>
<Separator />
<div className="bg-muted/30 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<Brain className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Model Selection</span>
</div>
<p className="text-xs text-muted-foreground">
Your selected model will be used for all knowledge graph
extractions. Changes apply to new generations only.
</p>
</div>
</>
)}
</div>
);
const renderAppearanceSection = () => (
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-4">Theme Selection</h3>
<div className="grid grid-cols-1 gap-3">
{themeOptions.map((option) => (
<Card
key={option.id}
className={`cursor-pointer transition-all hover:shadow-md ${
currentTheme === option.id
? "ring-2 ring-primary bg-primary/5"
: "hover:bg-muted/50"
}`}
onClick={() => handleThemeChange(option.id)}
>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-muted">
<option.icon className="h-4 w-4" />
</div>
<div>
<h4 className="font-medium">{option.name}</h4>
<p className="text-sm text-muted-foreground">
{option.description}
</p>
</div>
</div>
{currentTheme === option.id && (
<div className="p-1 rounded-full bg-primary">
<Check className="h-3 w-3 text-primary-foreground" />
</div>
)}
</div>
</CardContent>
</Card>
))}
</div>
</div>
<Separator />
<div>
<h3 className="text-lg font-semibold mb-4">Color Customization</h3>
<div className="bg-muted/30 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<Smartphone className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Coming Soon</span>
<Badge variant="secondary" className="text-xs">
Future Update
</Badge>
</div>
<p className="text-sm text-muted-foreground">
Custom color themes and accent colors will be available in a future
update.
</p>
</div>
</div>
</div>
);
const renderGeneralSection = () => (
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-4">Knowledge Graph Display</h3>
<div className="grid grid-cols-1 gap-3">
{[
{
id: "multiple" as const,
name: "Multiple Knowledge Graphs",
description:
"Show all knowledge graphs for research and comparison",
icon: Layers,
},
{
id: "single" as const,
name: "Single Knowledge Graph",
description:
"Show only the most recent/best knowledge graph per trace",
icon: GitBranch,
},
].map((option) => (
<Card
key={option.id}
className={`cursor-pointer transition-all hover:shadow-md ${
kgDisplayMode === option.id
? "ring-2 ring-primary bg-primary/5"
: "hover:bg-muted/50"
}`}
onClick={() => setKGDisplayMode(option.id)}
>
<CardContent className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-muted">
<option.icon className="h-4 w-4" />
</div>
<div>
<h4 className="font-medium">{option.name}</h4>
<p className="text-sm text-muted-foreground">
{option.description}
</p>
</div>
</div>
{kgDisplayMode === option.id && (
<div className="p-1 rounded-full bg-primary">
<Check className="h-3 w-3 text-primary-foreground" />
</div>
)}
</div>
</CardContent>
</Card>
))}
</div>
</div>
<Separator />
<div>
<h3 className="text-lg font-semibold mb-4">Additional Settings</h3>
<div className="bg-muted/30 p-4 rounded-lg">
<div className="flex items-center gap-2 mb-2">
<Settings className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">More Options</span>
<Badge variant="secondary" className="text-xs">
Coming Soon
</Badge>
</div>
<p className="text-sm text-muted-foreground">
More configuration options including language preferences, default
views, and notification settings will be available soon.
</p>
</div>
</div>
</div>
);
const renderSection = () => {
switch (activeSection) {
case "appearance":
return renderAppearanceSection();
case "models":
return renderModelsSection();
case "general":
return renderGeneralSection();
default:
return renderAppearanceSection();
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] p-0">
<DialogHeader className="p-6 pb-0">
<DialogTitle className="text-2xl font-bold flex items-center gap-2">
<Settings className="h-6 w-6" />
Settings & Preferences
</DialogTitle>
<DialogDescription className="text-base">
Customize your AgentGraph experience
</DialogDescription>
</DialogHeader>
<div className="flex flex-1 min-h-0">
{/* Navigation Sidebar */}
<div className="w-64 border-r bg-muted/20 p-4">
<nav className="space-y-2">
{sidebarSections.map((section) => (
<Button
key={section.id}
variant={activeSection === section.id ? "secondary" : "ghost"}
className="w-full justify-start gap-2 h-auto p-3"
onClick={() => setActiveSection(section.id)}
>
<section.icon className="h-4 w-4" />
<span className="text-sm font-medium">{section.name}</span>
</Button>
))}
</nav>
</div>
{/* Content Area */}
<div className="flex-1 min-w-0">
<ScrollArea className="h-[70vh] p-6">{renderSection()}</ScrollArea>
</div>
</div>
<Separator />
<div className="p-6 pt-4 flex justify-between items-center">
<div className="text-sm text-muted-foreground">
Changes are saved automatically
</div>
<Button onClick={() => onOpenChange(false)}>Done</Button>
</div>
</DialogContent>
</Dialog>
);
}