refactor(settings): unify agent tabs into single Agent section

Consolidate AI Model, Skills, Code Bridge, and Identity into
one Agent tab with pill-style sub-navigation. Reduces top-level
settings from 8 tabs to 5 (page) / 6 (modal).
This commit is contained in:
Nicholai Vogel 2026-02-16 18:54:45 -07:00
parent 7f5efb84e2
commit feb7dc2643
3 changed files with 102 additions and 84 deletions

View File

@ -3,20 +3,13 @@
import * as React from "react"
import {
IconAdjustments,
IconBrain,
IconPalette,
IconPlug,
IconPuzzle,
IconRobot,
IconTerminal2,
IconUsers,
} from "@tabler/icons-react"
import { useIsMobile } from "@/hooks/use-mobile"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
import {
Select,
SelectContent,
@ -24,14 +17,13 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Separator } from "@/components/ui/separator"
import { cn } from "@/lib/utils"
import { PreferencesTab } from "@/components/settings/preferences-tab"
import { AppearanceTab } from "@/components/settings/appearance-tab"
import { TeamTab } from "@/components/settings/team-tab"
import { AIModelTab } from "@/components/settings/ai-model-tab"
import { SkillsTab } from "@/components/settings/skills-tab"
import { ClaudeCodeTab } from "@/components/settings/claude-code-tab"
import { AgentTab } from "@/components/settings/agent-tab"
import { NetSuiteConnectionStatus } from "@/components/netsuite/connection-status"
import { SyncControls } from "@/components/netsuite/sync-controls"
import { GoogleDriveConnectionStatus } from "@/components/google/connection-status"
@ -40,46 +32,17 @@ const SETTINGS_TABS = [
{ value: "preferences", label: "Preferences", icon: IconAdjustments },
{ value: "appearance", label: "Theme", icon: IconPalette },
{ value: "team", label: "Team", icon: IconUsers },
{ value: "ai-model", label: "AI Model", icon: IconBrain },
{ value: "agent", label: "Agent", icon: IconRobot },
{ value: "skills", label: "Skills", icon: IconPuzzle },
{ value: "integrations", label: "Integrations", icon: IconPlug },
{ value: "claude-code", label: "Code Bridge", icon: IconTerminal2 },
] as const
type SectionValue = (typeof SETTINGS_TABS)[number]["value"]
// wide sections get unconstrained width for tables/complex layouts
const WIDE_SECTIONS = new Set<string>([
"appearance", "team", "ai-model", "claude-code",
"appearance", "team", "agent",
])
function AgentSection() {
const [signetId, setSignetId] = React.useState("")
return (
<div className="space-y-4">
<div className="space-y-1.5">
<Label htmlFor="signet-id" className="text-xs">
Signet ID (ETH)
</Label>
<Input
id="signet-id"
value={signetId}
onChange={(e) => setSignetId(e.target.value)}
placeholder="0x..."
className="h-9 max-w-sm font-mono"
type="password"
/>
</div>
<Separator />
<Button className="w-full max-w-sm">
Configure your agent
</Button>
</div>
)
}
function IntegrationsSection() {
return (
<div className="space-y-4">
@ -106,16 +69,10 @@ export default function SettingsPage() {
return <AppearanceTab />
case "team":
return <TeamTab />
case "ai-model":
return <AIModelTab />
case "agent":
return <AgentSection />
case "skills":
return <SkillsTab />
return <AgentTab />
case "integrations":
return <IntegrationsSection />
case "claude-code":
return <ClaudeCodeTab />
default:
return null
}

View File

@ -29,10 +29,8 @@ import { Textarea } from "@/components/ui/textarea"
import { NetSuiteConnectionStatus } from "@/components/netsuite/connection-status"
import { SyncControls } from "@/components/netsuite/sync-controls"
import { GoogleDriveConnectionStatus } from "@/components/google/connection-status"
import { SkillsTab } from "@/components/settings/skills-tab"
import { AIModelTab } from "@/components/settings/ai-model-tab"
import { AppearanceTab } from "@/components/settings/appearance-tab"
import { ClaudeCodeTab } from "@/components/settings/claude-code-tab"
import { AgentTab } from "@/components/settings/agent-tab"
import { TeamTab } from "@/components/settings/team-tab"
import { useNative } from "@/hooks/use-native"
import { useBiometricAuth } from "@/hooks/use-biometric-auth"
@ -44,9 +42,7 @@ const SETTINGS_TABS = [
{ value: "notifications", label: "Notifications" },
{ value: "appearance", label: "Theme" },
{ value: "integrations", label: "Integrations" },
{ value: "ai-model", label: "AI Model" },
{ value: "agent", label: "Agent" },
{ value: "skills", label: "Skills" },
] as const
const CREATE_SETTING_TAB = {
@ -91,7 +87,6 @@ export function SettingsModal({
const [pushNotifs, setPushNotifs] = React.useState(true)
const [weeklyDigest, setWeeklyDigest] = React.useState(false)
const [timezone, setTimezone] = React.useState("America/New_York")
const [signetId, setSignetId] = React.useState("")
const [customTabs, setCustomTabs] = React.useState<ReadonlyArray<CustomSettingTab>>([])
const [activeTab, setActiveTab] = React.useState<string>("general")
const [newSettingName, setNewSettingName] = React.useState("")
@ -263,41 +258,11 @@ export function SettingsModal({
<Separator />
<NetSuiteConnectionStatus />
<SyncControls />
<Separator />
<ClaudeCodeTab />
</div>
)
case "ai-model":
return <div className="pt-2"><AIModelTab /></div>
case "agent":
return (
<div className="space-y-4 pt-2">
<div className="space-y-1.5">
<Label htmlFor="signet-id" className="text-xs">
Signet ID (ETH)
</Label>
<Input
id="signet-id"
value={signetId}
onChange={(e) => setSignetId(e.target.value)}
placeholder="0x..."
className="h-9 font-mono"
type="password"
/>
</div>
<Separator />
<Button className="w-full">
Configure your agent
</Button>
</div>
)
case "skills":
return <div className="pt-2"><SkillsTab /></div>
return <div className="flex min-h-0 flex-1 pt-2"><AgentTab /></div>
case CREATE_SETTING_TAB.value:
return (

View File

@ -0,0 +1,96 @@
"use client"
import * as React from "react"
import {
BrainCircuit,
Blocks,
Cable,
Fingerprint,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
import { AIModelTab } from "@/components/settings/ai-model-tab"
import { SkillsTab } from "@/components/settings/skills-tab"
import { ClaudeCodeTab } from "@/components/settings/claude-code-tab"
import { cn } from "@/lib/utils"
const SUB_TABS = [
{ value: "model", label: "Model", icon: BrainCircuit },
{ value: "skills", label: "Skills", icon: Blocks },
{ value: "bridge", label: "Bridge", icon: Cable },
{ value: "identity", label: "Identity", icon: Fingerprint },
] as const
type SubTab = (typeof SUB_TABS)[number]["value"]
export function AgentTab(): React.ReactElement {
const [activeSubTab, setActiveSubTab] =
React.useState<SubTab>("model")
const [signetId, setSignetId] = React.useState("")
return (
<div className="flex min-h-0 flex-1 flex-col gap-4">
<div
className="flex gap-1.5"
role="radiogroup"
aria-label="Agent settings"
>
{SUB_TABS.map((tab) => {
const isActive = activeSubTab === tab.value
const Icon = tab.icon
return (
<button
key={tab.value}
type="button"
role="radio"
aria-checked={isActive}
onClick={() => setActiveSubTab(tab.value)}
className={cn(
"flex items-center gap-1.5 rounded-full px-3 py-1.5",
"text-xs font-medium transition-all",
isActive
? "bg-primary/10 text-primary ring-1 ring-primary/20"
: "text-muted-foreground hover:bg-muted/70"
)}
>
<Icon className="size-3.5" />
<span>{tab.label}</span>
</button>
)
})}
</div>
<div className="min-h-0 flex-1 overflow-y-auto">
{activeSubTab === "model" && <AIModelTab />}
{activeSubTab === "skills" && <SkillsTab />}
{activeSubTab === "bridge" && <ClaudeCodeTab />}
{activeSubTab === "identity" && (
<div className="space-y-4">
<div className="space-y-1.5">
<Label htmlFor="signet-id" className="text-xs">
Signet ID (ETH)
</Label>
<Input
id="signet-id"
value={signetId}
onChange={(e) => setSignetId(e.target.value)}
placeholder="0x..."
className="h-9 font-mono"
type="password"
/>
</div>
<Separator />
<Button className="w-full">
Configure your agent
</Button>
</div>
)}
</div>
</div>
)
}