"use client";
import { useState, useMemo, useCallback, useEffect } from "react";
import {
useAppStore,
AIProfile,
AgentModel,
ThinkingLevel,
ModelProvider,
} from "@/store/app-store";
import { Button } from "@/components/ui/button";
import { HotkeyButton } from "@/components/ui/hotkey-button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { cn, modelSupportsThinking } from "@/lib/utils";
import {
useKeyboardShortcuts,
useKeyboardShortcutsConfig,
KeyboardShortcut,
} from "@/hooks/use-keyboard-shortcuts";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
UserCircle,
Plus,
Pencil,
Trash2,
Brain,
Zap,
Scale,
Cpu,
Rocket,
Sparkles,
GripVertical,
Lock,
Check,
RefreshCw,
} from "lucide-react";
import { toast } from "sonner";
import {
DndContext,
DragEndEvent,
PointerSensor,
useSensor,
useSensors,
closestCenter,
} from "@dnd-kit/core";
import {
SortableContext,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
// Icon mapping for profiles
const PROFILE_ICONS: Record<
string,
React.ComponentType<{ className?: string }>
> = {
Brain,
Zap,
Scale,
Cpu,
Rocket,
Sparkles,
};
// Available icons for selection
const ICON_OPTIONS = [
{ name: "Brain", icon: Brain },
{ name: "Zap", icon: Zap },
{ name: "Scale", icon: Scale },
{ name: "Cpu", icon: Cpu },
{ name: "Rocket", icon: Rocket },
{ name: "Sparkles", icon: Sparkles },
];
// Model options for the form
const CLAUDE_MODELS: { id: AgentModel; label: string }[] = [
{ id: "haiku", label: "Claude Haiku" },
{ id: "sonnet", label: "Claude Sonnet" },
{ id: "opus", label: "Claude Opus" },
];
const THINKING_LEVELS: { id: ThinkingLevel; label: string }[] = [
{ id: "none", label: "None" },
{ id: "low", label: "Low" },
{ id: "medium", label: "Medium" },
{ id: "high", label: "High" },
{ id: "ultrathink", label: "Ultrathink" },
];
// Helper to determine provider from model
function getProviderFromModel(model: AgentModel): ModelProvider {
return "claude";
}
// Sortable Profile Card Component
function SortableProfileCard({
profile,
onEdit,
onDelete,
}: {
profile: AIProfile;
onEdit: () => void;
onDelete: () => void;
}) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: profile.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
};
const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : Brain;
return (
{/* Drag Handle */}
{/* Icon */}
{IconComponent && (
)}
{/* Content */}
{profile.name}
{profile.isBuiltIn && (
Built-in
)}
{profile.description}
{profile.model}
{profile.thinkingLevel !== "none" && (
{profile.thinkingLevel}
)}
{/* Actions */}
{!profile.isBuiltIn && (
)}
);
}
// Profile Form Component
function ProfileForm({
profile,
onSave,
onCancel,
isEditing,
hotkeyActive,
}: {
profile: Partial;
onSave: (profile: Omit) => void;
onCancel: () => void;
isEditing: boolean;
hotkeyActive: boolean;
}) {
const [formData, setFormData] = useState({
name: profile.name || "",
description: profile.description || "",
model: profile.model || ("opus" as AgentModel),
thinkingLevel: profile.thinkingLevel || ("none" as ThinkingLevel),
icon: profile.icon || "Brain",
});
const provider = getProviderFromModel(formData.model);
const supportsThinking = modelSupportsThinking(formData.model);
const handleModelChange = (model: AgentModel) => {
setFormData({
...formData,
model,
});
};
const handleSubmit = () => {
if (!formData.name.trim()) {
toast.error("Please enter a profile name");
return;
}
onSave({
name: formData.name.trim(),
description: formData.description.trim(),
model: formData.model,
thinkingLevel: supportsThinking ? formData.thinkingLevel : "none",
provider,
isBuiltIn: false,
icon: formData.icon,
});
};
return (
{/* Name */}
setFormData({ ...formData, name: e.target.value })}
placeholder="e.g., Heavy Task, Quick Fix"
data-testid="profile-name-input"
/>
{/* Description */}
{/* Icon Selection */}
{ICON_OPTIONS.map(({ name, icon: Icon }) => (
))}
{/* Model Selection */}
{CLAUDE_MODELS.map(({ id, label }) => (
))}
{/* Thinking Level */}
{supportsThinking && (
{THINKING_LEVELS.map(({ id, label }) => (
))}
Higher levels give more time to reason through complex problems.
)}
{/* Actions */}
{isEditing ? "Save Changes" : "Create Profile"}
);
}
export function ProfilesView() {
const {
aiProfiles,
addAIProfile,
updateAIProfile,
removeAIProfile,
reorderAIProfiles,
resetAIProfiles,
} = useAppStore();
const shortcuts = useKeyboardShortcutsConfig();
const [showAddDialog, setShowAddDialog] = useState(false);
const [editingProfile, setEditingProfile] = useState(null);
// Sensors for drag-and-drop
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 5,
},
})
);
// Separate built-in and custom profiles
const builtInProfiles = useMemo(
() => aiProfiles.filter((p) => p.isBuiltIn),
[aiProfiles]
);
const customProfiles = useMemo(
() => aiProfiles.filter((p) => !p.isBuiltIn),
[aiProfiles]
);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
const { active, over } = event;
if (over && active.id !== over.id) {
const oldIndex = aiProfiles.findIndex((p) => p.id === active.id);
const newIndex = aiProfiles.findIndex((p) => p.id === over.id);
if (oldIndex !== -1 && newIndex !== -1) {
reorderAIProfiles(oldIndex, newIndex);
}
}
},
[aiProfiles, reorderAIProfiles]
);
const handleAddProfile = (profile: Omit) => {
addAIProfile(profile);
setShowAddDialog(false);
toast.success("Profile created", {
description: `Created "${profile.name}" profile`,
});
};
const handleUpdateProfile = (profile: Omit) => {
if (editingProfile) {
updateAIProfile(editingProfile.id, profile);
setEditingProfile(null);
toast.success("Profile updated", {
description: `Updated "${profile.name}" profile`,
});
}
};
const handleDeleteProfile = (profile: AIProfile) => {
if (profile.isBuiltIn) return;
removeAIProfile(profile.id);
toast.success("Profile deleted", {
description: `Deleted "${profile.name}" profile`,
});
};
const handleResetProfiles = () => {
resetAIProfiles();
toast.success("Profiles refreshed", {
description: "Default profiles have been updated to the latest version",
});
};
// Build keyboard shortcuts for profiles view
const profilesShortcuts: KeyboardShortcut[] = useMemo(() => {
const shortcutsList: KeyboardShortcut[] = [];
// Add profile shortcut - when in profiles view
shortcutsList.push({
key: shortcuts.addProfile,
action: () => setShowAddDialog(true),
description: "Create new profile",
});
return shortcutsList;
}, [shortcuts]);
// Register keyboard shortcuts for profiles view
useKeyboardShortcuts(profilesShortcuts);
return (
{/* Header Section */}
AI Profiles
Create and manage model configuration presets
setShowAddDialog(true)}
hotkey={shortcuts.addProfile}
hotkeyActive={false}
data-testid="add-profile-button"
>
New Profile
{/* Content */}
{/* Custom Profiles Section */}
Custom Profiles
{customProfiles.length}
{customProfiles.length === 0 ? (
No custom profiles yet. Create one to get started!
) : (
p.id)}
strategy={verticalListSortingStrategy}
>
{customProfiles.map((profile) => (
setEditingProfile(profile)}
onDelete={() => handleDeleteProfile(profile)}
/>
))}
)}
{/* Built-in Profiles Section */}
Built-in Profiles
{builtInProfiles.length}
Pre-configured profiles for common use cases. These cannot be
edited or deleted.
p.id)}
strategy={verticalListSortingStrategy}
>
{builtInProfiles.map((profile) => (
{}}
onDelete={() => {}}
/>
))}
{/* Add Profile Dialog */}
{/* Edit Profile Dialog */}
);
}