chore: 清理40个死代码文件 (~9,639行)
删除无任何活跃渲染路径引用的组件: - Automation/ 全目录 (7文件, 2,598行) - WorkflowBuilder/ 全目录 (14文件, 1,539行) - SchedulerPanel + 依赖树 (5文件, 2,595行) - 独立死组件 (14文件, 2,907行) 含 SkillMarket, HandsPanel, ErrorNotification 等 - PipelineResultPreview 根目录副本 (534行, 活跃版在 pipeline/)
This commit is contained in:
@@ -1,345 +0,0 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
AlertTriangle,
|
||||
Wifi,
|
||||
Shield,
|
||||
Clock,
|
||||
Settings,
|
||||
AlertCircle,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Copy,
|
||||
CheckCircle,
|
||||
ExternalLink,
|
||||
} from 'lucide-react';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Button } from './Button';
|
||||
import {
|
||||
AppError,
|
||||
ErrorCategory,
|
||||
classifyError,
|
||||
formatErrorForClipboard,
|
||||
getErrorIcon as getIconByCategory,
|
||||
getErrorColor as getColorByCategory,
|
||||
} from '../../lib/error-types';
|
||||
|
||||
import { reportError } from '../../lib/error-handling';
|
||||
|
||||
// === Props ===
|
||||
|
||||
export interface ErrorAlertProps {
|
||||
error: AppError | string | Error | null;
|
||||
onDismiss?: () => void;
|
||||
onRetry?: () => void;
|
||||
showTechnicalDetails?: boolean;
|
||||
className?: string;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
interface ErrorAlertState {
|
||||
showDetails: boolean;
|
||||
copied: boolean;
|
||||
}
|
||||
|
||||
// === Category Configuration ===
|
||||
|
||||
const CATEGORY_CONFIG: Record<ErrorCategory, {
|
||||
icon: typeof Wifi | typeof Shield | typeof Clock | typeof Settings | typeof AlertCircle | typeof AlertTriangle;
|
||||
color: string;
|
||||
bgColor: string;
|
||||
label: string;
|
||||
}> = {
|
||||
network: {
|
||||
icon: Wifi,
|
||||
color: 'text-orange-500',
|
||||
bgColor: 'bg-orange-50 dark:bg-orange-900/20',
|
||||
label: 'Network',
|
||||
},
|
||||
auth: {
|
||||
icon: Shield,
|
||||
color: 'text-red-500',
|
||||
bgColor: 'bg-red-50 dark:bg-red-900/20',
|
||||
label: 'Authentication',
|
||||
},
|
||||
permission: {
|
||||
icon: Shield,
|
||||
color: 'text-purple-500',
|
||||
bgColor: 'bg-purple-50 dark:bg-purple-900/20',
|
||||
label: 'Permission',
|
||||
},
|
||||
validation: {
|
||||
icon: AlertCircle,
|
||||
color: 'text-yellow-600',
|
||||
bgColor: 'bg-yellow-50 dark:bg-yellow-900/20',
|
||||
label: 'Validation',
|
||||
},
|
||||
timeout: {
|
||||
icon: Clock,
|
||||
color: 'text-amber-500',
|
||||
bgColor: 'bg-amber-50 dark:bg-amber-900/20',
|
||||
label: 'Timeout',
|
||||
},
|
||||
server: {
|
||||
icon: AlertTriangle,
|
||||
color: 'text-red-500',
|
||||
bgColor: 'bg-red-50 dark:bg-red-900/20',
|
||||
label: 'Server',
|
||||
},
|
||||
client: {
|
||||
icon: AlertCircle,
|
||||
color: 'text-blue-500',
|
||||
bgColor: 'bg-blue-50 dark:bg-blue-900/20',
|
||||
label: 'Client',
|
||||
},
|
||||
config: {
|
||||
icon: Settings,
|
||||
color: 'text-gray-500',
|
||||
bgColor: 'bg-gray-50 dark:bg-gray-900/20',
|
||||
label: 'Configuration',
|
||||
},
|
||||
system: {
|
||||
icon: AlertTriangle,
|
||||
color: 'text-red-600',
|
||||
bgColor: 'bg-red-50 dark:bg-red-900/20',
|
||||
label: 'System',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get icon component for error category
|
||||
*/
|
||||
export function getIconByCategory(category: ErrorCategory): typeof Wifi | typeof Shield | typeof Clock | typeof Settings | typeof AlertCircle | typeof AlertTriangle {
|
||||
return CATEGORY_CONFIG[category]?.icon ?? AlertCircle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color class for error category
|
||||
*/
|
||||
export function getColorByCategory(category: ErrorCategory): string {
|
||||
return CATEGORY_CONFIG[category]?.color ?? 'text-gray-500';
|
||||
}
|
||||
|
||||
/**
|
||||
* ErrorAlert Component
|
||||
*
|
||||
* Displays detailed error information with recovery suggestions,
|
||||
* technical details, and action buttons.
|
||||
*/
|
||||
export function ErrorAlert({
|
||||
error: errorProp,
|
||||
onDismiss,
|
||||
onRetry,
|
||||
showTechnicalDetails = true,
|
||||
className,
|
||||
compact = false,
|
||||
}: ErrorAlertProps) {
|
||||
const [state, setState] = useState<ErrorAlertState>({
|
||||
showDetails: false,
|
||||
copied: false,
|
||||
});
|
||||
|
||||
// Normalize error input
|
||||
const appError = typeof errorProp === 'string'
|
||||
? classifyError(new Error(errorProp))
|
||||
: errorProp instanceof Error
|
||||
? classifyError(errorProp)
|
||||
: errorProp;
|
||||
|
||||
const {
|
||||
category,
|
||||
title,
|
||||
message,
|
||||
technicalDetails,
|
||||
recoverable,
|
||||
recoverySteps,
|
||||
timestamp,
|
||||
} = appError;
|
||||
|
||||
const config = CATEGORY_CONFIG[category] || CATEGORY_CONFIG.system!;
|
||||
const IconComponent = config.icon;
|
||||
|
||||
const handleCopyDetails = useCallback(async () => {
|
||||
const text = formatErrorForClipboard(appError);
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setState({ copied: true });
|
||||
setTimeout(() => setState({ copied: false }), 2000);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy error details:', err);
|
||||
}
|
||||
}, [appError]);
|
||||
|
||||
const handleReport = useCallback(() => {
|
||||
reportError(appError.originalError || appError, {
|
||||
errorId: appError.id,
|
||||
category: appError.category,
|
||||
title: appError.title,
|
||||
message: appError.message,
|
||||
timestamp: appError.timestamp.toISOString(),
|
||||
});
|
||||
}, [appError]);
|
||||
|
||||
const toggleDetails = useCallback(() => {
|
||||
setState((prev) => ({ showDetails: !prev.showDetails }));
|
||||
}, []);
|
||||
|
||||
const handleRetry = useCallback(() => {
|
||||
onRetry?.();
|
||||
}, [onRetry]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className={cn(
|
||||
'rounded-lg border overflow-hidden',
|
||||
config.bgColor,
|
||||
'border-gray-200 dark:border-gray-700',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-start gap-3 p-3 bg-white/50 dark:bg-gray-800/50">
|
||||
<div className={cn('p-2 rounded-lg', config.bgColor)}>
|
||||
<IconComponent className={cn('w-5 h-5', config.color)} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn('text-xs font-medium', config.color)}>
|
||||
{config.label}
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
{timestamp.toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
<h4 className="text-sm font-medium text-gray-900 dark:text-gray-100 mt-1">
|
||||
{title}
|
||||
</h4>
|
||||
</div>
|
||||
{onDismiss && (
|
||||
<button
|
||||
onClick={onDismiss}
|
||||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 p-1"
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="px-3 pb-2">
|
||||
<p className={cn(
|
||||
'text-gray-700 dark:text-gray-300',
|
||||
compact ? 'text-sm line-clamp-2' : 'text-sm'
|
||||
)}>
|
||||
{message}
|
||||
</p>
|
||||
|
||||
{/* Recovery Steps */}
|
||||
{recoverySteps.length > 0 && !compact && (
|
||||
<div className="mt-3 space-y-2">
|
||||
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 flex items-center gap-1">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
Recovery Suggestions
|
||||
</p>
|
||||
<ul className="space-y-1">
|
||||
{recoverySteps.slice(0, 3).map((step, index) => (
|
||||
<li key={index} className="text-xs text-gray-600 dark:text-gray-400 flex items-start gap-2">
|
||||
<span className="text-gray-400">-</span>
|
||||
{step.description}
|
||||
{step.action && step.label && (
|
||||
<button
|
||||
onClick={step.action}
|
||||
className="text-blue-500 hover:text-blue-600 ml-1"
|
||||
>
|
||||
{step.label}
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Technical Details Toggle */}
|
||||
{showTechnicalDetails && technicalDetails && !compact && (
|
||||
<div className="mt-2">
|
||||
<button
|
||||
onClick={toggleDetails}
|
||||
className="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
||||
>
|
||||
{state.showDetails ? (
|
||||
<ChevronUp className="w-3 h-3" />
|
||||
) : (
|
||||
<ChevronDown className="w-3 h-3" />
|
||||
)}
|
||||
Technical Details
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{state.showDetails && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<pre className="mt-2 p-2 bg-gray-100 dark:bg-gray-800 rounded text-xs text-gray-600 dark:text-gray-400 overflow-x-auto whitespace-pre-wrap break-all">
|
||||
{technicalDetails}
|
||||
</pre>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-between gap-2 p-3 pt-2 border-t border-gray-100 dark:border-gray-700 bg-white/30 dark:bg-gray-800/30">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleCopyDetails}
|
||||
className="text-xs"
|
||||
>
|
||||
{state.copied ? (
|
||||
<>
|
||||
<CheckCircle className="w-3 h-3 mr-1" />
|
||||
Copied
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-3 h-3 mr-1" />
|
||||
Copy
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleReport}
|
||||
className="text-xs"
|
||||
>
|
||||
<ExternalLink className="w-3 h-3 mr-1" />
|
||||
Report
|
||||
</Button>
|
||||
</div>
|
||||
{recoverable && onRetry && (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={handleRetry}
|
||||
className="text-xs"
|
||||
>
|
||||
Try Again
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user