Polish UI components and rework AgentConfigForm
Major AgentConfigForm rework with improved adapter configuration fields and layout. Refine sidebar, breadcrumbs, and card/tab components for visual consistency. Clean up page layouts across Activity, Agents, Approvals, Costs, Dashboard, Goals, Inbox, Issues, Org, and Projects pages. Minor heartbeat-run CLI fix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -101,8 +101,7 @@ export function Activity() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">Activity</h2>
|
||||
<div className="flex items-center justify-end">
|
||||
<Select value={filter} onValueChange={setFilter}>
|
||||
<SelectTrigger className="w-[140px] h-8 text-xs">
|
||||
<SelectValue placeholder="Filter by type" />
|
||||
@@ -126,7 +125,7 @@ export function Activity() {
|
||||
)}
|
||||
|
||||
{filtered && filtered.length > 0 && (
|
||||
<div className="border border-border rounded-md divide-y divide-border">
|
||||
<div className="border border-border divide-y divide-border">
|
||||
{filtered.map((event) => {
|
||||
const link = entityLink(event.entityType, event.entityId);
|
||||
return (
|
||||
|
||||
@@ -367,7 +367,7 @@ export function AgentDetail() {
|
||||
{assignedIssues.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No assigned issues.</p>
|
||||
) : (
|
||||
<div className="border border-border rounded-md">
|
||||
<div className="border border-border">
|
||||
{assignedIssues.map((issue) => (
|
||||
<EntityRow
|
||||
key={issue.id}
|
||||
@@ -429,6 +429,7 @@ function ConfigurationTab({ agent }: { agent: Agent }) {
|
||||
mode="edit"
|
||||
agent={agent}
|
||||
onSave={(patch) => updateAgent.mutate(patch)}
|
||||
isSaving={updateAgent.isPending}
|
||||
adapterModels={adapterModels}
|
||||
/>
|
||||
</div>
|
||||
@@ -450,7 +451,7 @@ function RunsTab({ runs, companyId }: { runs: HeartbeatRun[]; companyId: string
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="border border-border rounded-md">
|
||||
<div className="border border-border">
|
||||
{sorted.map((run) => {
|
||||
const statusInfo = runStatusIcons[run.status] ?? { icon: Clock, color: "text-neutral-400" };
|
||||
const StatusIcon = statusInfo.icon;
|
||||
@@ -975,7 +976,7 @@ function KeysTab({ agentId }: { agentId: string }) {
|
||||
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-2">
|
||||
Active Keys
|
||||
</h3>
|
||||
<div className="border border-border rounded-md divide-y divide-border">
|
||||
<div className="border border-border divide-y divide-border">
|
||||
{activeKeys.map((key: AgentKey) => (
|
||||
<div key={key.id} className="flex items-center justify-between px-4 py-2.5">
|
||||
<div>
|
||||
@@ -1005,7 +1006,7 @@ function KeysTab({ agentId }: { agentId: string }) {
|
||||
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-2">
|
||||
Revoked Keys
|
||||
</h3>
|
||||
<div className="border border-border rounded-md divide-y divide-border opacity-50">
|
||||
<div className="border border-border divide-y divide-border opacity-50">
|
||||
{revokedKeys.map((key: AgentKey) => (
|
||||
<div key={key.id} className="flex items-center justify-between px-4 py-2.5">
|
||||
<div>
|
||||
|
||||
@@ -93,20 +93,17 @@ export function Agents() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<h2 className="text-lg font-semibold">Agents</h2>
|
||||
<Tabs value={tab} onValueChange={(v) => setTab(v as FilterTab)}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="all">All{agents ? ` (${agents.length})` : ""}</TabsTrigger>
|
||||
<TabsTrigger value="active">Active</TabsTrigger>
|
||||
<TabsTrigger value="paused">Paused</TabsTrigger>
|
||||
<TabsTrigger value="error">Error</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<Tabs value={tab} onValueChange={(v) => setTab(v as FilterTab)}>
|
||||
<TabsList variant="line">
|
||||
<TabsTrigger value="all">All{agents ? ` (${agents.length})` : ""}</TabsTrigger>
|
||||
<TabsTrigger value="active">Active</TabsTrigger>
|
||||
<TabsTrigger value="paused">Paused</TabsTrigger>
|
||||
<TabsTrigger value="error">Error</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* View toggle */}
|
||||
<div className="flex items-center border border-border rounded-md">
|
||||
<div className="flex items-center border border-border">
|
||||
<button
|
||||
className={cn(
|
||||
"p-1.5 transition-colors",
|
||||
@@ -147,7 +144,7 @@ export function Agents() {
|
||||
|
||||
{/* List view */}
|
||||
{view === "list" && filtered.length > 0 && (
|
||||
<div className="border border-border rounded-md">
|
||||
<div className="border border-border">
|
||||
{filtered.map((agent) => {
|
||||
const budgetPct =
|
||||
agent.budgetMonthlyCents > 0
|
||||
@@ -221,7 +218,7 @@ export function Agents() {
|
||||
|
||||
{/* Org chart view */}
|
||||
{view === "org" && filteredOrg.length > 0 && (
|
||||
<div className="border border-border rounded-md py-1">
|
||||
<div className="border border-border py-1">
|
||||
{filteredOrg.map((node) => (
|
||||
<OrgTreeNode key={node.id} node={node} depth={0} navigate={navigate} agentMap={agentMap} />
|
||||
))}
|
||||
@@ -275,7 +272,7 @@ function OrgTreeNode({
|
||||
return (
|
||||
<div style={{ paddingLeft: depth * 24 }}>
|
||||
<button
|
||||
className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-accent/30 transition-colors w-full text-left"
|
||||
className="flex items-center gap-3 px-3 py-2 hover:bg-accent/30 transition-colors w-full text-left"
|
||||
onClick={() => navigate(`/agents/${node.id}`)}
|
||||
>
|
||||
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
||||
|
||||
@@ -227,25 +227,22 @@ export function Approvals() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<h2 className="text-lg font-semibold">Approvals</h2>
|
||||
<Tabs value={statusFilter} onValueChange={(v) => setStatusFilter(v as StatusFilter)}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="pending">
|
||||
Pending
|
||||
{pendingCount > 0 && (
|
||||
<span className={cn(
|
||||
"ml-1.5 rounded-full px-1.5 py-0.5 text-[10px] font-medium",
|
||||
"bg-yellow-500/20 text-yellow-500"
|
||||
)}>
|
||||
{pendingCount}
|
||||
</span>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="all">All</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<Tabs value={statusFilter} onValueChange={(v) => setStatusFilter(v as StatusFilter)}>
|
||||
<TabsList variant="line">
|
||||
<TabsTrigger value="pending">
|
||||
Pending
|
||||
{pendingCount > 0 && (
|
||||
<span className={cn(
|
||||
"ml-1.5 rounded-full px-1.5 py-0.5 text-[10px] font-medium",
|
||||
"bg-yellow-500/20 text-yellow-500"
|
||||
)}>
|
||||
{pendingCount}
|
||||
</span>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="all">All</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{isLoading && <p className="text-sm text-muted-foreground">Loading...</p>}
|
||||
|
||||
@@ -89,11 +89,7 @@ export function Companies() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Companies</h2>
|
||||
<p className="text-sm text-muted-foreground">Manage your companies.</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-end">
|
||||
<Button size="sm" onClick={openOnboarding}>
|
||||
<Plus className="h-3.5 w-3.5 mr-1.5" />
|
||||
New Company
|
||||
|
||||
@@ -36,8 +36,6 @@ export function Costs() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold">Costs</h2>
|
||||
|
||||
{isLoading && <p className="text-sm text-muted-foreground">Loading...</p>}
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
|
||||
|
||||
@@ -113,12 +113,9 @@ export function Dashboard() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Dashboard</h2>
|
||||
{selectedCompany && (
|
||||
<p className="text-sm text-muted-foreground">{selectedCompany.name}</p>
|
||||
)}
|
||||
</div>
|
||||
{selectedCompany && (
|
||||
<p className="text-sm text-muted-foreground">{selectedCompany.name}</p>
|
||||
)}
|
||||
|
||||
{isLoading && <p className="text-sm text-muted-foreground">Loading...</p>}
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
@@ -159,7 +156,7 @@ export function Dashboard() {
|
||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
Recent Activity
|
||||
</h3>
|
||||
<div className="border border-border rounded-md divide-y divide-border">
|
||||
<div className="border border-border divide-y divide-border">
|
||||
{activity.slice(0, 10).map((event) => (
|
||||
<div key={event.id} className="px-4 py-2 flex items-center justify-between text-sm">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
@@ -185,11 +182,11 @@ export function Dashboard() {
|
||||
Stale Tasks
|
||||
</h3>
|
||||
{staleIssues.length === 0 ? (
|
||||
<div className="border border-border rounded-md p-4">
|
||||
<div className="border border-border p-4">
|
||||
<p className="text-sm text-muted-foreground">No stale tasks. All work is up to date.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="border border-border rounded-md divide-y divide-border">
|
||||
<div className="border border-border divide-y divide-border">
|
||||
{staleIssues.slice(0, 10).map((issue) => (
|
||||
<div
|
||||
key={issue.id}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function GoalDetail() {
|
||||
{linkedProjects.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No linked projects.</p>
|
||||
) : (
|
||||
<div className="border border-border rounded-md">
|
||||
<div className="border border-border">
|
||||
{linkedProjects.map((project) => (
|
||||
<EntityRow
|
||||
key={project.id}
|
||||
|
||||
@@ -30,8 +30,6 @@ export function Goals() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Goals</h2>
|
||||
|
||||
{isLoading && <p className="text-sm text-muted-foreground">Loading...</p>}
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
|
||||
|
||||
@@ -116,8 +116,6 @@ export function Inbox() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold">Inbox</h2>
|
||||
|
||||
{isLoading && <p className="text-sm text-muted-foreground">Loading...</p>}
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
{actionError && <p className="text-sm text-destructive">{actionError}</p>}
|
||||
@@ -140,7 +138,7 @@ export function Inbox() {
|
||||
See all approvals <ExternalLink className="inline h-3 w-3 ml-0.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="border border-border rounded-md divide-y divide-border">
|
||||
<div className="border border-border divide-y divide-border">
|
||||
{approvals!.map((approval) => (
|
||||
<div key={approval.id} className="p-4 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -191,7 +189,7 @@ export function Inbox() {
|
||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
Alerts
|
||||
</h3>
|
||||
<div className="border border-border rounded-md divide-y divide-border">
|
||||
<div className="border border-border divide-y divide-border">
|
||||
{dashboard!.agents.error > 0 && (
|
||||
<div
|
||||
className="px-4 py-3 flex items-center gap-3 cursor-pointer hover:bg-accent/50 transition-colors"
|
||||
@@ -232,7 +230,7 @@ export function Inbox() {
|
||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
Stale Work
|
||||
</h3>
|
||||
<div className="border border-border rounded-md divide-y divide-border">
|
||||
<div className="border border-border divide-y divide-border">
|
||||
{staleIssues.map((issue) => (
|
||||
<div
|
||||
key={issue.id}
|
||||
|
||||
@@ -89,17 +89,14 @@ export function Issues() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<h2 className="text-lg font-semibold">Issues</h2>
|
||||
<Tabs value={tab} onValueChange={(v) => setTab(v as TabFilter)}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="all">All Issues</TabsTrigger>
|
||||
<TabsTrigger value="active">Active</TabsTrigger>
|
||||
<TabsTrigger value="backlog">Backlog</TabsTrigger>
|
||||
<TabsTrigger value="done">Done</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<Tabs value={tab} onValueChange={(v) => setTab(v as TabFilter)}>
|
||||
<TabsList variant="line">
|
||||
<TabsTrigger value="all">All Issues</TabsTrigger>
|
||||
<TabsTrigger value="active">Active</TabsTrigger>
|
||||
<TabsTrigger value="backlog">Backlog</TabsTrigger>
|
||||
<TabsTrigger value="done">Done</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<Button size="sm" onClick={() => openNewIssue()}>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
New Issue
|
||||
@@ -120,7 +117,7 @@ export function Issues() {
|
||||
|
||||
{orderedGroups.map(({ status, items }) => (
|
||||
<div key={status}>
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-muted/50 rounded-t-md">
|
||||
<div className="flex items-center gap-2 px-4 py-2 bg-muted/50">
|
||||
<StatusIcon status={status} />
|
||||
<span className="text-xs font-semibold uppercase tracking-wide">
|
||||
{statusLabel(status)}
|
||||
@@ -135,7 +132,7 @@ export function Issues() {
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="border border-border rounded-b-md">
|
||||
<div className="border border-border">
|
||||
{items.map((issue) => (
|
||||
<EntityRow
|
||||
key={issue.id}
|
||||
|
||||
@@ -38,8 +38,6 @@ export function MyIssues() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">My Issues</h2>
|
||||
|
||||
{isLoading && <p className="text-sm text-muted-foreground">Loading...</p>}
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
|
||||
@@ -48,7 +46,7 @@ export function MyIssues() {
|
||||
)}
|
||||
|
||||
{myIssues.length > 0 && (
|
||||
<div className="border border-border rounded-md">
|
||||
<div className="border border-border">
|
||||
{myIssues.map((issue) => (
|
||||
<EntityRow
|
||||
key={issue.id}
|
||||
|
||||
@@ -106,8 +106,6 @@ export function Org() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Org Chart</h2>
|
||||
|
||||
{isLoading && <p className="text-sm text-muted-foreground">Loading...</p>}
|
||||
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
||||
|
||||
@@ -119,7 +117,7 @@ export function Org() {
|
||||
)}
|
||||
|
||||
{data && data.length > 0 && (
|
||||
<div className="border border-border rounded-md py-1">
|
||||
<div className="border border-border py-1">
|
||||
<OrgTree nodes={data} onSelect={(id) => navigate(`/agents/${id}`)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -87,7 +87,7 @@ export function ProjectDetail() {
|
||||
{projectIssues.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No issues in this project.</p>
|
||||
) : (
|
||||
<div className="border border-border rounded-md">
|
||||
<div className="border border-border">
|
||||
{projectIssues.map((issue) => (
|
||||
<EntityRow
|
||||
key={issue.id}
|
||||
|
||||
@@ -35,8 +35,7 @@ export function Projects() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">Projects</h2>
|
||||
<div className="flex items-center justify-end">
|
||||
<Button size="sm" onClick={openNewProject}>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Add Project
|
||||
@@ -56,7 +55,7 @@ export function Projects() {
|
||||
)}
|
||||
|
||||
{projects && projects.length > 0 && (
|
||||
<div className="border border-border rounded-md">
|
||||
<div className="border border-border">
|
||||
{projects.map((project) => (
|
||||
<EntityRow
|
||||
key={project.id}
|
||||
|
||||
Reference in New Issue
Block a user