mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
- Added A2UITypes for defining question structures and answers. - Created A2UIWebSocketHandler for managing WebSocket connections and message handling. - Developed ask-question tool for interactive user questions via A2UI. - Introduced platformUtils for platform detection and shell command handling. - Centralized TypeScript types in index.ts for better organization. - Implemented compatibility checks for hook templates based on platform requirements.
239 lines
7.1 KiB
TypeScript
239 lines
7.1 KiB
TypeScript
// ========================================
|
|
// Hook Card Component
|
|
// ========================================
|
|
// Individual hook display card with actions
|
|
|
|
import { useIntl } from 'react-intl';
|
|
import {
|
|
GitFork,
|
|
Power,
|
|
PowerOff,
|
|
Edit,
|
|
Trash2,
|
|
ChevronDown,
|
|
ChevronUp,
|
|
} from 'lucide-react';
|
|
import { Card } from '@/components/ui/Card';
|
|
import { Button } from '@/components/ui/Button';
|
|
import { Badge } from '@/components/ui/Badge';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
// ========== Types ==========
|
|
|
|
export type HookTriggerType = 'UserPromptSubmit' | 'PreToolUse' | 'PostToolUse' | 'Stop';
|
|
|
|
export interface HookCardData {
|
|
name: string;
|
|
description?: string;
|
|
enabled: boolean;
|
|
trigger: HookTriggerType;
|
|
matcher?: string;
|
|
command?: string;
|
|
script?: string;
|
|
}
|
|
|
|
export interface HookCardProps {
|
|
hook: HookCardData;
|
|
isExpanded: boolean;
|
|
onToggleExpand: () => void;
|
|
onToggle: (hookName: string, enabled: boolean) => void;
|
|
onEdit: (hook: HookCardData) => void;
|
|
onDelete: (hookName: string) => void;
|
|
}
|
|
|
|
// ========== Helper Functions ==========
|
|
|
|
function getTriggerIcon(trigger: HookTriggerType) {
|
|
switch (trigger) {
|
|
case 'UserPromptSubmit':
|
|
return '⚡';
|
|
case 'PreToolUse':
|
|
return '🔧';
|
|
case 'PostToolUse':
|
|
return '✅';
|
|
case 'Stop':
|
|
return '🛑';
|
|
default:
|
|
return '📌';
|
|
}
|
|
}
|
|
|
|
function getTriggerVariant(trigger: HookTriggerType): 'default' | 'secondary' | 'outline' {
|
|
switch (trigger) {
|
|
case 'UserPromptSubmit':
|
|
return 'default';
|
|
case 'PreToolUse':
|
|
return 'secondary';
|
|
case 'PostToolUse':
|
|
return 'outline';
|
|
case 'Stop':
|
|
return 'secondary';
|
|
default:
|
|
return 'outline';
|
|
}
|
|
}
|
|
|
|
// ========== Component ==========
|
|
|
|
export function HookCard({
|
|
hook,
|
|
isExpanded,
|
|
onToggleExpand,
|
|
onToggle,
|
|
onEdit,
|
|
onDelete,
|
|
}: HookCardProps) {
|
|
const { formatMessage } = useIntl();
|
|
|
|
const handleToggle = () => {
|
|
onToggle(hook.name, !hook.enabled);
|
|
};
|
|
|
|
const handleEdit = () => {
|
|
onEdit(hook);
|
|
};
|
|
|
|
const handleDelete = () => {
|
|
if (confirm(formatMessage({ id: 'cliHooks.actions.deleteConfirm' }, { hookName: hook.name }))) {
|
|
onDelete(hook.name);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card className={cn('overflow-hidden', !hook.enabled && 'opacity-60')}>
|
|
{/* Header */}
|
|
<div className="p-4">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
|
<div className={cn(
|
|
'p-2 rounded-lg flex-shrink-0',
|
|
hook.enabled ? 'bg-primary/10' : 'bg-muted'
|
|
)}>
|
|
<GitFork className={cn(
|
|
'w-4 h-4',
|
|
hook.enabled ? 'text-primary' : 'text-muted-foreground'
|
|
)} />
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<span className="text-sm font-medium text-foreground truncate">
|
|
{hook.name}
|
|
</span>
|
|
<Badge
|
|
variant={getTriggerVariant(hook.trigger)}
|
|
className="text-xs flex-shrink-0"
|
|
>
|
|
<span className="mr-1">{getTriggerIcon(hook.trigger)}</span>
|
|
{formatMessage({ id: `cliHooks.trigger.${hook.trigger}` })}
|
|
</Badge>
|
|
<Badge
|
|
variant={hook.enabled ? 'default' : 'secondary'}
|
|
className="text-xs flex-shrink-0"
|
|
>
|
|
{hook.enabled
|
|
? formatMessage({ id: 'common.status.enabled' })
|
|
: formatMessage({ id: 'common.status.disabled' })
|
|
}
|
|
</Badge>
|
|
</div>
|
|
{hook.description && (
|
|
<p className="text-xs text-muted-foreground mt-0.5 truncate">
|
|
{hook.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex items-center gap-1 flex-shrink-0">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-8 w-8 p-0"
|
|
onClick={handleToggle}
|
|
title={hook.enabled
|
|
? formatMessage({ id: 'cliHooks.actions.disable' })
|
|
: formatMessage({ id: 'cliHooks.actions.enable' })
|
|
}
|
|
>
|
|
{hook.enabled ? (
|
|
<Power className="w-4 h-4 text-success" />
|
|
) : (
|
|
<PowerOff className="w-4 h-4 text-muted-foreground" />
|
|
)}
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-8 w-8 p-0"
|
|
onClick={handleEdit}
|
|
title={formatMessage({ id: 'common.actions.edit' })}
|
|
>
|
|
<Edit className="w-4 h-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-8 w-8 p-0 text-destructive hover:text-destructive hover:bg-destructive/10"
|
|
onClick={handleDelete}
|
|
title={formatMessage({ id: 'common.actions.delete' })}
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-8 w-8 p-0"
|
|
onClick={onToggleExpand}
|
|
title={isExpanded
|
|
? formatMessage({ id: 'cliHooks.actions.collapse' })
|
|
: formatMessage({ id: 'cliHooks.actions.expand' })
|
|
}
|
|
>
|
|
{isExpanded ? (
|
|
<ChevronUp className="w-4 h-4" />
|
|
) : (
|
|
<ChevronDown className="w-4 h-4" />
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Expanded Content */}
|
|
{isExpanded && (
|
|
<div className="border-t border-border bg-muted/30 p-4 space-y-3">
|
|
{hook.description && (
|
|
<div>
|
|
<label className="text-xs font-medium text-muted-foreground">
|
|
{formatMessage({ id: 'cliHooks.form.description' })}
|
|
</label>
|
|
<p className="text-sm text-foreground mt-1">{hook.description}</p>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<label className="text-xs font-medium text-muted-foreground">
|
|
{formatMessage({ id: 'cliHooks.form.matcher' })}
|
|
</label>
|
|
<p className="text-sm text-foreground mt-1 font-mono bg-muted px-2 py-1 rounded">
|
|
{hook.matcher || formatMessage({ id: 'cliHooks.allTools' })}
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-xs font-medium text-muted-foreground">
|
|
{formatMessage({ id: 'cliHooks.form.command' })}
|
|
</label>
|
|
<p className="text-sm text-foreground mt-1 font-mono bg-muted px-2 py-1 rounded break-all max-h-32 overflow-y-auto">
|
|
{hook.command || hook.script || 'N/A'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export default HookCard;
|