feat: implement hooks system for project configuration

- Add HooksEditor component for managing project hooks
- Add ProjectSettings component for project-specific configurations
- Create hooksManager utility for hook operations
- Add hooks type definitions
- Update backend commands to support hooks functionality
- Integrate hooks into main app, agent execution, and Claude sessions
- Update API and utilities to handle hooks data
This commit is contained in:
Vivek R
2025-07-06 14:10:44 +05:30
parent 1922ffc145
commit 6b9393f4d3
15 changed files with 2294 additions and 311 deletions

View File

@@ -11,12 +11,21 @@ import {
Copy,
ChevronDown,
Maximize2,
X
X,
Settings2
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Popover } from "@/components/ui/popover";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { api, type Agent } from "@/lib/api";
import { cn } from "@/lib/utils";
import { open } from "@tauri-apps/plugin-dialog";
@@ -25,6 +34,8 @@ import { StreamMessage } from "./StreamMessage";
import { ExecutionControlBar } from "./ExecutionControlBar";
import { ErrorBoundary } from "./ErrorBoundary";
import { useVirtualizer } from "@tanstack/react-virtual";
import { AGENT_ICONS } from "./CCAgents";
import { HooksEditor } from "./HooksEditor";
interface AgentExecutionProps {
/**
@@ -78,6 +89,10 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
const [error, setError] = useState<string | null>(null);
const [copyPopoverOpen, setCopyPopoverOpen] = useState(false);
// Hooks configuration state
const [isHooksDialogOpen, setIsHooksDialogOpen] = useState(false);
const [activeHooksTab, setActiveHooksTab] = useState("project");
// Execution stats
const [executionStartTime, setExecutionStartTime] = useState<number | null>(null);
const [totalTokens, setTotalTokens] = useState(0);
@@ -266,6 +281,10 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
}
};
const handleOpenHooksDialog = async () => {
setIsHooksDialogOpen(true);
};
const handleExecute = async () => {
try {
setIsRunning(true);
@@ -501,86 +520,41 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className="flex items-center justify-between p-4"
className="p-6"
>
<div className="flex items-center space-x-3">
<Button
variant="ghost"
size="icon"
onClick={handleBackWithConfirmation}
className="h-8 w-8"
>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex items-center gap-2">
{renderIcon()}
<div>
<div className="flex items-center gap-2">
<h2 className="text-lg font-semibold">{agent.name}</h2>
{isRunning && (
<div className="flex items-center gap-1">
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
<span className="text-xs text-green-600 font-medium">Running</span>
</div>
)}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Button
variant="ghost"
size="icon"
onClick={handleBackWithConfirmation}
className="h-8 w-8"
>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex items-center gap-3">
<div className="p-2 rounded-full bg-primary/10 text-primary">
{renderIcon()}
</div>
<div>
<h1 className="text-xl font-bold">Execute: {agent.name}</h1>
<p className="text-sm text-muted-foreground">
{model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'}
</p>
</div>
<p className="text-xs text-muted-foreground">
{isRunning ? "Click back to return to main menu - view in CC Agents > Running Sessions" : "Execute CC Agent"}
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2">
{messages.length > 0 && (
<>
<Button
variant="ghost"
size="sm"
onClick={() => setIsFullscreenModalOpen(true)}
className="flex items-center gap-2"
>
<Maximize2 className="h-4 w-4" />
Fullscreen
</Button>
<Popover
trigger={
<Button
variant="ghost"
size="sm"
className="flex items-center gap-2"
>
<Copy className="h-4 w-4" />
Copy Output
<ChevronDown className="h-3 w-3" />
</Button>
}
content={
<div className="w-44 p-1">
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={handleCopyAsJsonl}
>
Copy as JSONL
</Button>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={handleCopyAsMarkdown}
>
Copy as Markdown
</Button>
</div>
}
open={copyPopoverOpen}
onOpenChange={setCopyPopoverOpen}
align="end"
/>
</>
)}
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setIsFullscreenModalOpen(true)}
disabled={messages.length === 0}
>
<Maximize2 className="h-4 w-4 mr-2" />
Fullscreen
</Button>
</div>
</div>
</motion.div>
</div>
@@ -620,6 +594,15 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
>
<FolderOpen className="h-4 w-4" />
</Button>
<Button
variant="outline"
onClick={handleOpenHooksDialog}
disabled={isRunning || !projectPath}
title="Configure hooks"
>
<Settings2 className="h-4 w-4 mr-2" />
Hooks
</Button>
</div>
</div>
@@ -931,9 +914,56 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
</div>
</div>
)}
{/* Hooks Configuration Dialog */}
<Dialog
open={isHooksDialogOpen}
onOpenChange={setIsHooksDialogOpen}
>
<DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle>Configure Hooks</DialogTitle>
<DialogDescription>
Configure hooks that run before, during, and after tool executions. Changes are saved immediately.
</DialogDescription>
</DialogHeader>
<Tabs value={activeHooksTab} onValueChange={setActiveHooksTab} className="flex-1 flex flex-col overflow-hidden">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="project">Project Settings</TabsTrigger>
<TabsTrigger value="local">Local Settings</TabsTrigger>
</TabsList>
<TabsContent value="project" className="flex-1 overflow-auto">
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
Project hooks are stored in <code className="bg-muted px-1 py-0.5 rounded">.claude/settings.json</code> and
are committed to version control.
</p>
<HooksEditor
projectPath={projectPath}
scope="project"
className="border-0"
/>
</div>
</TabsContent>
<TabsContent value="local" className="flex-1 overflow-auto">
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
Local hooks are stored in <code className="bg-muted px-1 py-0.5 rounded">.claude/settings.local.json</code> and
are not committed to version control.
</p>
<HooksEditor
projectPath={projectPath}
scope="local"
className="border-0"
/>
</div>
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
</div>
);
};
// Import AGENT_ICONS for icon rendering
import { AGENT_ICONS } from "./CCAgents";