mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat(a2ui): Implement A2UI backend with question handling and WebSocket support
- 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.
This commit is contained in:
238
ccw/frontend/src/components/hook/HookCard.tsx
Normal file
238
ccw/frontend/src/components/hook/HookCard.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
// ========================================
|
||||
// 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;
|
||||
Reference in New Issue
Block a user