diff --git a/apps/web/app/settings/agent/[id]/page.tsx b/apps/web/app/settings/agent/[id]/page.tsx new file mode 100644 index 0000000000..f1fc90abd4 --- /dev/null +++ b/apps/web/app/settings/agent/[id]/page.tsx @@ -0,0 +1,258 @@ +'use client'; + +import { use, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { IconTrash } from '@tabler/icons-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; +import { Separator } from '@/components/ui/separator'; +import { Switch } from '@/components/ui/switch'; +import { Slider } from '@/components/ui/slider'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { SETTINGS_DATA } from '@/lib/settings/dummy-data'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import type { AgentProfile, AgentType } from '@/lib/settings/types'; + +const AGENT_LABELS: Record = { + 'claude-code': 'Claude Code', + 'codex': 'Codex', + 'auggie': 'Auggie', +}; + +const MODEL_OPTIONS: Record = { + 'claude-code': [ + { value: 'claude-sonnet-4.5', label: 'Claude Sonnet 4.5' }, + { value: 'claude-opus-4', label: 'Claude Opus 4' }, + { value: 'claude-haiku-4', label: 'Claude Haiku 4' }, + ], + 'codex': [ + { value: 'codex-v2', label: 'Codex v2' }, + { value: 'codex-v1', label: 'Codex v1' }, + ], + 'auggie': [ + { value: 'auggie-v1', label: 'Auggie v1' }, + { value: 'auggie-lite', label: 'Auggie Lite' }, + ], +}; + +export default function AgentEditPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = use(params); + const router = useRouter(); + const [agent, setAgent] = useState( + SETTINGS_DATA.agents.find((a) => a.id === id) + ); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [deleteConfirmText, setDeleteConfirmText] = useState(''); + + if (!agent) { + return ( +
+ + +

Agent not found

+ +
+
+
+ ); + } + + const handleDeleteAgent = () => { + if (deleteConfirmText === 'delete') { + router.push('/settings/agents'); + setDeleteDialogOpen(false); + } + }; + + return ( +
+
+

{agent.name}

+

+ Configure agent behavior and capabilities +

+
+ + + + + + Agent Name + + + setAgent({ ...agent, name: e.target.value })} + placeholder="Code Assistant" + /> + + + + + + Agent Configuration + + +
+
+ + +
+
+ + +
+
+
+
+ + + + Advanced Settings + + +
+
+
+ +

+ Controls randomness in responses (0 = deterministic, 1 = creative) +

+
+ {agent.temperature.toFixed(1)} +
+ setAgent({ ...agent, temperature: value })} + min={0} + max={1} + step={0.1} + className="w-full" + /> +
+ + + +
+
+ +

+ Automatically execute tool calls without confirmation +

+
+ setAgent({ ...agent, autoApprove: checked })} + /> +
+
+
+ + + + {/* Danger Zone */} + + + Danger Zone + + Irreversible actions that will permanently delete this agent + + + +
+
+

Delete this agent

+

+ Once deleted, all configuration will be permanently removed +

+
+ +
+
+
+ + {/* Delete Confirmation Dialog */} + + + + Delete Agent + + This action cannot be undone. This will permanently delete the agent " + {agent.name}" and all its data. + + +
+

+ Please type delete to confirm: +

+ setDeleteConfirmText(e.target.value)} + placeholder="delete" + /> +
+ + + + +
+
+
+ ); +} diff --git a/apps/web/app/settings/agents/page.tsx b/apps/web/app/settings/agents/page.tsx new file mode 100644 index 0000000000..62c8421280 --- /dev/null +++ b/apps/web/app/settings/agents/page.tsx @@ -0,0 +1,238 @@ +'use client'; + +import { useState } from 'react'; +import { IconPlus } from '@tabler/icons-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card, CardContent } from '@/components/ui/card'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Separator } from '@/components/ui/separator'; +import { AgentCard } from '@/components/settings/agent-card'; +import { SETTINGS_DATA } from '@/lib/settings/dummy-data'; +import type { AgentProfile, AgentType } from '@/lib/settings/types'; + +const AGENT_OPTIONS: { value: AgentType; label: string }[] = [ + { value: 'claude-code', label: 'Claude Code' }, + { value: 'codex', label: 'Codex' }, + { value: 'auggie', label: 'Auggie' }, +]; + +const MODEL_OPTIONS: Record = { + 'claude-code': [ + { value: 'claude-sonnet-4.5', label: 'Claude Sonnet 4.5' }, + { value: 'claude-opus-4', label: 'Claude Opus 4' }, + { value: 'claude-haiku-4', label: 'Claude Haiku 4' }, + ], + 'codex': [ + { value: 'codex-v2', label: 'Codex v2' }, + { value: 'codex-v1', label: 'Codex v1' }, + ], + 'auggie': [ + { value: 'auggie-v1', label: 'Auggie v1' }, + { value: 'auggie-lite', label: 'Auggie Lite' }, + ], +}; + +export default function AgentsSettingsPage() { + const [agents, setAgents] = useState(SETTINGS_DATA.agents); + const [isAdding, setIsAdding] = useState(false); + const [newAgent, setNewAgent] = useState<{ + agent: AgentType; + name: string; + model: string; + autoApprove: boolean; + temperature: number; + }>({ + agent: 'claude-code', + name: '', + model: 'claude-sonnet-4.5', + autoApprove: false, + temperature: 0.7, + }); + + const handleUpdate = (agent: AgentProfile) => { + setAgents(agents.map((a) => (a.id === agent.id ? agent : a))); + }; + + const handleDelete = (id: string) => { + setAgents(agents.filter((a) => a.id !== id)); + }; + + const handleAdd = (e: React.FormEvent) => { + e.preventDefault(); + const profile: AgentProfile = { + id: crypto.randomUUID(), + ...newAgent, + }; + setAgents([...agents, profile]); + setIsAdding(false); + setNewAgent({ + agent: 'claude-code', + name: '', + model: 'claude-sonnet-4.5', + autoApprove: false, + temperature: 0.7, + }); + }; + + const handleAgentTypeChange = (type: AgentType) => { + const defaultModel = MODEL_OPTIONS[type][0].value; + setNewAgent({ ...newAgent, agent: type, model: defaultModel }); + }; + + return ( +
+
+
+

Agents

+

+ Configure agent profiles and their behavior +

+
+ +
+ + + +
+
+ {isAdding && ( + + +
+
+ + setNewAgent({ ...newAgent, name: e.target.value })} + placeholder="My Profile" + required + /> +
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + + setNewAgent({ ...newAgent, temperature: parseFloat(e.target.value) }) + } + className="w-full h-2 bg-muted rounded-lg appearance-none cursor-pointer" + /> +
+ Precise + Creative +
+
+ +
+
+ +

+ Automatically approve agent actions without prompting +

+
+ +
+ +
+ + +
+
+
+
+ )} + + {agents.map((agent) => ( + + ))} + + {agents.length === 0 && !isAdding && ( + + +

+ No agent profiles configured. Add your first profile to get started. +

+
+
+ )} +
+
+
+ ); +} diff --git a/apps/web/app/settings/environment/[id]/page.tsx b/apps/web/app/settings/environment/[id]/page.tsx new file mode 100644 index 0000000000..cea4613a88 --- /dev/null +++ b/apps/web/app/settings/environment/[id]/page.tsx @@ -0,0 +1,366 @@ +'use client'; + +import { use, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { IconPlus, IconTrash, IconX } from '@tabler/icons-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; +import { Separator } from '@/components/ui/separator'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { SETTINGS_DATA } from '@/lib/settings/dummy-data'; +import type { Environment, EnvironmentType, BaseDocker } from '@/lib/settings/types'; + +export default function EnvironmentEditPage({ params }: { params: Promise<{ id: string }> }) { + const { id } = use(params); + const router = useRouter(); + const [environment, setEnvironment] = useState( + SETTINGS_DATA.environments.find((e) => e.id === id) + ); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [deleteConfirmText, setDeleteConfirmText] = useState(''); + const [newEnvKey, setNewEnvKey] = useState(''); + const [newEnvValue, setNewEnvValue] = useState(''); + const [newSecretKey, setNewSecretKey] = useState(''); + const [newSecretValue, setNewSecretValue] = useState(''); + + if (!environment) { + return ( +
+ + +

Environment not found

+ +
+
+
+ ); + } + + const handleAddEnvVar = () => { + if (newEnvKey.trim()) { + setEnvironment({ + ...environment, + envVariables: [ + ...environment.envVariables, + { id: crypto.randomUUID(), key: newEnvKey, value: newEnvValue }, + ], + }); + setNewEnvKey(''); + setNewEnvValue(''); + } + }; + + const handleRemoveEnvVar = (id: string) => { + setEnvironment({ + ...environment, + envVariables: environment.envVariables.filter((v) => v.id !== id), + }); + }; + + const handleAddSecret = () => { + if (newSecretKey.trim()) { + setEnvironment({ + ...environment, + secrets: [ + ...environment.secrets, + { id: crypto.randomUUID(), key: newSecretKey, value: newSecretValue }, + ], + }); + setNewSecretKey(''); + setNewSecretValue(''); + } + }; + + const handleRemoveSecret = (id: string) => { + setEnvironment({ + ...environment, + secrets: environment.secrets.filter((s) => s.id !== id), + }); + }; + + const handleToggleAgent = (agentId: string) => { + const installed = environment.installedAgents || []; + if (installed.includes(agentId)) { + setEnvironment({ + ...environment, + installedAgents: installed.filter((id) => id !== agentId), + }); + } else { + setEnvironment({ + ...environment, + installedAgents: [...installed, agentId], + }); + } + }; + + const handleDeleteEnvironment = () => { + if (deleteConfirmText === 'delete') { + router.push('/settings/environments'); + setDeleteDialogOpen(false); + } + }; + + return ( +
+
+

{environment.name}

+

+ Configure Docker environment and runtime settings +

+
+ + + + + + Environment Name + + + setEnvironment({ ...environment, name: e.target.value })} + /> + + + + + + Docker Configuration + + +
+
+ + +
+
+ + +
+
+
+ +