feat(settings): move utility agents to main agents page (#436)
* feat(settings): move utility agents to main agents page - Extract utility agents section into reusable component - Add utility agents section to main agents settings page - Remove separate utility agents page and sidebar link - Consolidate all agent-related settings in one location * style: format utility-agents-section.tsx with prettier * fix: add error handling for utility agents operations - Add try/catch with rollback for handleDefaultChange - Add try/catch for onDelete to prevent UI inconsistency on failure Addresses CodeRabbit review comments.
This commit is contained in:
@@ -29,6 +29,7 @@ import { useAgentDiscovery } from "@/hooks/domains/settings/use-agent-discovery"
|
||||
import { useAvailableAgents } from "@/hooks/domains/settings/use-available-agents";
|
||||
import { AgentLogo } from "@/components/agent-logo";
|
||||
import { AddTUIAgentDialog } from "@/components/settings/add-tui-agent-dialog";
|
||||
import { UtilityAgentsSection } from "@/components/settings/utility-agents-section";
|
||||
import { toAgentProfileOption } from "@/lib/state/slices/settings/types";
|
||||
import type {
|
||||
AgentDiscovery,
|
||||
@@ -565,6 +566,8 @@ export default function AgentsSettingsPage() {
|
||||
|
||||
<ConfigChatAgentSection />
|
||||
|
||||
<UtilityAgentsSection />
|
||||
|
||||
<AddTUIAgentDialog
|
||||
open={tuiDialogOpen}
|
||||
onOpenChange={setTuiDialogOpen}
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
IconMessageCircle,
|
||||
IconBrandGithub,
|
||||
IconSparkles,
|
||||
IconWand,
|
||||
} from "@tabler/icons-react";
|
||||
import {
|
||||
Sidebar,
|
||||
@@ -299,16 +298,6 @@ export function SettingsAppSidebar() {
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
{/* Utility Agents */}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild isActive={pathname === "/settings/utility"}>
|
||||
<Link href="/settings/utility">
|
||||
<IconWand className="h-4 w-4" />
|
||||
<span>Utility Agents</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
<ExecutorsSidebarSection pathname={pathname} executors={executors} />
|
||||
<SecretsSidebarSection pathname={pathname} />
|
||||
|
||||
|
||||
+52
-40
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, useCallback, useMemo } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Separator } from "@kandev/ui/separator";
|
||||
import {
|
||||
listUtilityAgents,
|
||||
@@ -43,7 +43,7 @@ async function handleBuiltinChange(
|
||||
);
|
||||
}
|
||||
|
||||
export default function UtilityAgentsSettingsPage() {
|
||||
export function UtilityAgentsSection() {
|
||||
const [agents, setAgents] = useState<UtilityAgent[]>([]);
|
||||
const [inferenceAgents, setInferenceAgents] = useState<InferenceAgent[]>([]);
|
||||
const [defaultAgentId, setDefaultAgentId] = useState("");
|
||||
@@ -75,14 +75,17 @@ export default function UtilityAgentsSettingsPage() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
const handleDefaultChange = async (agentId: string, model: string) => {
|
||||
const prevAgentId = defaultAgentId;
|
||||
const prevModel = defaultModel;
|
||||
setDefaultAgentId(agentId);
|
||||
setDefaultModel(model);
|
||||
await updateUserSettings({ default_utility_agent_id: agentId, default_utility_model: model });
|
||||
try {
|
||||
await updateUserSettings({ default_utility_agent_id: agentId, default_utility_model: model });
|
||||
} catch {
|
||||
setDefaultAgentId(prevAgentId);
|
||||
setDefaultModel(prevModel);
|
||||
}
|
||||
};
|
||||
|
||||
const openEditDialog = (agent: UtilityAgent | null) => {
|
||||
@@ -96,46 +99,55 @@ export default function UtilityAgentsSettingsPage() {
|
||||
fetchData();
|
||||
};
|
||||
|
||||
if (loading)
|
||||
return <div className="py-8 text-center text-muted-foreground text-sm">Loading...</div>;
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
if (loading) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<header>
|
||||
<h2 className="text-xl font-semibold">Utility Agents</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
One-shot AI helpers for commits, PRs, and prompts.
|
||||
</p>
|
||||
</header>
|
||||
<Separator />
|
||||
<DefaultModelSection
|
||||
inferenceAgents={inferenceAgents}
|
||||
defaultAgentId={defaultAgentId}
|
||||
defaultModel={defaultModel}
|
||||
onDefaultChange={handleDefaultChange}
|
||||
/>
|
||||
<PerActionOverridesSection
|
||||
builtins={builtins}
|
||||
allModels={allModels}
|
||||
defaultModel={defaultModel}
|
||||
onModelChange={(agent, value) => handleBuiltinChange(agent, value, setAgents)}
|
||||
onEdit={openEditDialog}
|
||||
/>
|
||||
<CustomAgentsSection
|
||||
agents={customAgents}
|
||||
onAdd={() => openEditDialog(null)}
|
||||
onEdit={openEditDialog}
|
||||
onDelete={async (agent) => {
|
||||
await deleteUtilityAgent(agent.id);
|
||||
setAgents((prev) => prev.filter((a) => a.id !== agent.id));
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
<Separator />
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Utility Agents</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
One-shot AI helpers for commits, PRs, and prompts.
|
||||
</p>
|
||||
</div>
|
||||
<DefaultModelSection
|
||||
inferenceAgents={inferenceAgents}
|
||||
defaultAgentId={defaultAgentId}
|
||||
defaultModel={defaultModel}
|
||||
onDefaultChange={handleDefaultChange}
|
||||
/>
|
||||
<PerActionOverridesSection
|
||||
builtins={builtins}
|
||||
allModels={allModels}
|
||||
defaultModel={defaultModel}
|
||||
onModelChange={(agent, value) => handleBuiltinChange(agent, value, setAgents)}
|
||||
onEdit={openEditDialog}
|
||||
/>
|
||||
<CustomAgentsSection
|
||||
agents={customAgents}
|
||||
onAdd={() => openEditDialog(null)}
|
||||
onEdit={openEditDialog}
|
||||
onDelete={async (agent) => {
|
||||
try {
|
||||
await deleteUtilityAgent(agent.id);
|
||||
setAgents((prev) => prev.filter((a) => a.id !== agent.id));
|
||||
} catch {
|
||||
// Error already logged by API layer
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<UtilityAgentDialog
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
agent={editingAgent}
|
||||
onSuccess={closeDialog}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user