refactor(frontend): 简化审计页面UI样式,移除多余动画效果
- 清理代理树节点和日志条目的冗余样式和动画 - 统一颜色和间距设计,优化暗黑模式支持 - 移除背景特效和多余装饰元素,提升性能
This commit is contained in:
parent
0bfed4d7d4
commit
87c501b55c
|
|
@ -1,8 +1,6 @@
|
|||
/**
|
||||
* Agent Tree Node Component
|
||||
* Elegant tree visualization with enhanced visual design
|
||||
* Features: Animated connection lines, status indicators, smooth transitions
|
||||
* Premium visual effects with depth and hierarchy
|
||||
* Clean tree visualization with simple connection lines
|
||||
*/
|
||||
|
||||
import { useState, memo } from "react";
|
||||
|
|
@ -11,37 +9,29 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { AGENT_STATUS_CONFIG } from "../constants";
|
||||
import type { AgentTreeNodeItemProps } from "../types";
|
||||
|
||||
// Agent type icons with enhanced colors and styling
|
||||
// Agent type icons
|
||||
const AGENT_TYPE_ICONS: Record<string, React.ReactNode> = {
|
||||
orchestrator: <Cpu className="w-4 h-4 text-violet-500" />,
|
||||
recon: <Scan className="w-4 h-4 text-teal-500" />,
|
||||
analysis: <FileSearch className="w-4 h-4 text-amber-500" />,
|
||||
verification: <ShieldCheck className="w-4 h-4 text-emerald-500" />,
|
||||
orchestrator: <Cpu className="w-4 h-4 text-violet-600 dark:text-violet-500" />,
|
||||
recon: <Scan className="w-4 h-4 text-teal-600 dark:text-teal-500" />,
|
||||
analysis: <FileSearch className="w-4 h-4 text-amber-600 dark:text-amber-500" />,
|
||||
verification: <ShieldCheck className="w-4 h-4 text-emerald-600 dark:text-emerald-500" />,
|
||||
};
|
||||
|
||||
// Agent type background colors for icon container
|
||||
// Agent type background colors
|
||||
const AGENT_TYPE_BG: Record<string, string> = {
|
||||
orchestrator: 'bg-violet-500/15 border-violet-500/30',
|
||||
recon: 'bg-teal-500/15 border-teal-500/30',
|
||||
analysis: 'bg-amber-500/15 border-amber-500/30',
|
||||
verification: 'bg-emerald-500/15 border-emerald-500/30',
|
||||
};
|
||||
|
||||
// Status colors for the glow effect
|
||||
const STATUS_GLOW_COLORS: Record<string, string> = {
|
||||
running: 'shadow-[0_0_15px_rgba(52,211,153,0.3)]',
|
||||
completed: 'shadow-[0_0_10px_rgba(52,211,153,0.15)]',
|
||||
failed: 'shadow-[0_0_12px_rgba(244,63,94,0.25)]',
|
||||
waiting: 'shadow-[0_0_8px_rgba(251,191,36,0.2)]',
|
||||
created: '',
|
||||
orchestrator: 'bg-violet-100 dark:bg-violet-500/15 border-violet-300 dark:border-violet-500/30',
|
||||
recon: 'bg-teal-100 dark:bg-teal-500/15 border-teal-300 dark:border-teal-500/30',
|
||||
analysis: 'bg-amber-100 dark:bg-amber-500/15 border-amber-300 dark:border-amber-500/30',
|
||||
verification: 'bg-emerald-100 dark:bg-emerald-500/15 border-emerald-300 dark:border-emerald-500/30',
|
||||
};
|
||||
|
||||
export const AgentTreeNodeItem = memo(function AgentTreeNodeItem({
|
||||
node,
|
||||
depth = 0,
|
||||
selectedId,
|
||||
onSelect
|
||||
}: AgentTreeNodeItemProps) {
|
||||
onSelect,
|
||||
isLast = false
|
||||
}: AgentTreeNodeItemProps & { isLast?: boolean }) {
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
const isSelected = selectedId === node.agent_id;
|
||||
|
|
@ -49,65 +39,62 @@ export const AgentTreeNodeItem = memo(function AgentTreeNodeItem({
|
|||
const isCompleted = node.status === 'completed';
|
||||
const isFailed = node.status === 'failed';
|
||||
|
||||
const statusConfig = AGENT_STATUS_CONFIG[node.status] || AGENT_STATUS_CONFIG.created;
|
||||
const typeIcon = AGENT_TYPE_ICONS[node.agent_type] || <Bot className="w-3.5 h-3.5 text-muted-foreground" />;
|
||||
const typeBg = AGENT_TYPE_BG[node.agent_type] || 'bg-muted/50 border-border/50';
|
||||
const typeBg = AGENT_TYPE_BG[node.agent_type] || 'bg-muted border-border';
|
||||
|
||||
const indent = depth * 24;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* Connection line to parent - vertical line with gradient */}
|
||||
{depth > 0 && (
|
||||
<div
|
||||
className="absolute top-0 w-px bg-gradient-to-b from-border/80 via-border/50 to-border/30"
|
||||
style={{
|
||||
left: `${depth * 20 - 10}px`,
|
||||
height: '22px',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Horizontal connector line with dot */}
|
||||
{/* 树形连接线 */}
|
||||
{depth > 0 && (
|
||||
<>
|
||||
{/* 垂直线 - 从父节点延伸下来 */}
|
||||
<div
|
||||
className="absolute top-[22px] h-px bg-gradient-to-r from-border/60 to-border/30"
|
||||
className="absolute border-l-2 border-slate-300 dark:border-slate-600"
|
||||
style={{
|
||||
left: `${depth * 20 - 10}px`,
|
||||
width: '10px',
|
||||
left: `${indent - 12}px`,
|
||||
top: 0,
|
||||
height: isLast ? '20px' : '100%',
|
||||
}}
|
||||
/>
|
||||
{/* 水平线 - 连接到当前节点 */}
|
||||
<div
|
||||
className="absolute top-[20px] w-1.5 h-1.5 rounded-full bg-border/60"
|
||||
className="absolute border-t-2 border-slate-300 dark:border-slate-600"
|
||||
style={{
|
||||
left: `${depth * 20 - 11}px`,
|
||||
left: `${indent - 12}px`,
|
||||
top: '20px',
|
||||
width: '12px',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Node item with enhanced styling */}
|
||||
{/* Node item */}
|
||||
<div
|
||||
className={`
|
||||
group relative flex items-center gap-2.5 py-2.5 px-3 cursor-pointer rounded-lg
|
||||
transition-all duration-300 ease-out backdrop-blur-sm
|
||||
relative flex items-center gap-2 py-2 px-2 cursor-pointer rounded-md
|
||||
${isSelected
|
||||
? 'bg-primary/15 border border-primary/50 shadow-[0_0_20px_rgba(255,107,44,0.15)]'
|
||||
: 'border border-transparent hover:bg-card/60 hover:border-border/50'
|
||||
? 'bg-primary/15 border-2 border-primary shadow-[0_0_12px_rgba(255,95,31,0.4)]'
|
||||
: isRunning
|
||||
? 'bg-emerald-50 dark:bg-emerald-950/30 border-2 border-emerald-400 dark:border-emerald-500 shadow-[0_0_10px_rgba(52,211,153,0.3)]'
|
||||
: isCompleted
|
||||
? 'bg-slate-50 dark:bg-card border border-emerald-300 dark:border-emerald-600'
|
||||
: isFailed
|
||||
? 'bg-rose-50 dark:bg-rose-950/20 border border-rose-300 dark:border-rose-500'
|
||||
: node.status === 'waiting'
|
||||
? 'bg-amber-50 dark:bg-amber-950/20 border border-amber-300 dark:border-amber-500'
|
||||
: 'bg-slate-50 dark:bg-card border border-slate-300 dark:border-slate-600 hover:border-slate-400 dark:hover:border-slate-500'
|
||||
}
|
||||
${STATUS_GLOW_COLORS[node.status] || ''}
|
||||
`}
|
||||
style={{ marginLeft: `${depth * 20}px` }}
|
||||
style={{ marginLeft: `${indent}px` }}
|
||||
onClick={() => onSelect(node.agent_id)}
|
||||
>
|
||||
{/* Expand/collapse button with enhanced styling */}
|
||||
{/* Expand/collapse button */}
|
||||
{hasChildren ? (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setExpanded(!expanded); }}
|
||||
className={`
|
||||
flex-shrink-0 w-6 h-6 flex items-center justify-center rounded-md
|
||||
transition-all duration-300
|
||||
${expanded ? 'bg-muted/80 border border-border/50' : 'hover:bg-muted/50'}
|
||||
`}
|
||||
className="flex-shrink-0 w-5 h-5 flex items-center justify-center rounded hover:bg-muted"
|
||||
>
|
||||
{expanded ? (
|
||||
<ChevronDown className="w-4 h-4 text-muted-foreground" />
|
||||
|
|
@ -116,81 +103,65 @@ export const AgentTreeNodeItem = memo(function AgentTreeNodeItem({
|
|||
)}
|
||||
</button>
|
||||
) : (
|
||||
<span className="w-6" />
|
||||
<span className="w-5" />
|
||||
)}
|
||||
|
||||
{/* Status indicator with enhanced glow */}
|
||||
{/* Status indicator */}
|
||||
<div className="relative flex-shrink-0">
|
||||
<div className={`
|
||||
w-3 h-3 rounded-full transition-all duration-300
|
||||
${isRunning ? 'bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.6)]' : ''}
|
||||
${isCompleted ? 'bg-emerald-500 shadow-[0_0_6px_rgba(52,211,153,0.4)]' : ''}
|
||||
${isFailed ? 'bg-rose-400 shadow-[0_0_6px_rgba(244,63,94,0.4)]' : ''}
|
||||
${node.status === 'waiting' ? 'bg-amber-400 shadow-[0_0_6px_rgba(251,191,36,0.4)]' : ''}
|
||||
${node.status === 'created' ? 'bg-muted-foreground/50' : ''}
|
||||
w-2.5 h-2.5 rounded-full
|
||||
${isRunning ? 'bg-emerald-500' : ''}
|
||||
${isCompleted ? 'bg-emerald-500' : ''}
|
||||
${isFailed ? 'bg-rose-500' : ''}
|
||||
${node.status === 'waiting' ? 'bg-amber-500' : ''}
|
||||
${node.status === 'created' ? 'bg-slate-400' : ''}
|
||||
`} />
|
||||
{isRunning && (
|
||||
<>
|
||||
<div className="absolute inset-0 w-3 h-3 rounded-full bg-emerald-400 animate-ping opacity-40" />
|
||||
<div className="absolute inset-[-2px] w-[calc(100%+4px)] h-[calc(100%+4px)] rounded-full border border-emerald-400/30 animate-pulse" />
|
||||
</>
|
||||
<div className="absolute inset-0 w-2.5 h-2.5 rounded-full bg-emerald-500 animate-ping opacity-50" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Agent type icon with background */}
|
||||
<div className={`flex-shrink-0 p-1.5 rounded-md border ${typeBg} transition-all duration-300 group-hover:scale-105`}>
|
||||
{/* Agent type icon */}
|
||||
<div className={`flex-shrink-0 p-1 rounded border ${typeBg}`}>
|
||||
{typeIcon}
|
||||
</div>
|
||||
|
||||
{/* Agent name with enhanced styling */}
|
||||
{/* Agent name */}
|
||||
<span className={`
|
||||
text-sm font-mono truncate flex-1 transition-all duration-300
|
||||
${isSelected ? 'text-foreground font-semibold' : 'text-foreground/90 group-hover:text-foreground'}
|
||||
text-sm font-mono truncate flex-1
|
||||
${isSelected ? 'text-foreground font-semibold' : 'text-foreground'}
|
||||
`}>
|
||||
{node.agent_name}
|
||||
</span>
|
||||
|
||||
{/* Metrics badges with enhanced styling */}
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
{/* Iterations with icon */}
|
||||
{/* Metrics */}
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||
{(node.iterations ?? 0) > 0 && (
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground font-mono bg-muted/80 px-2 py-1 rounded-md border border-border/50">
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground font-mono bg-muted px-1.5 py-0.5 rounded border border-border">
|
||||
<Zap className="w-3 h-3" />
|
||||
<span>{node.iterations}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Findings count - Only show for Orchestrator (root agent) */}
|
||||
{!node.parent_agent_id && node.findings_count > 0 && (
|
||||
<Badge className="h-6 px-2.5 text-xs bg-rose-500/20 text-rose-600 dark:text-rose-300 border border-rose-500/40 font-mono font-bold shadow-[0_0_10px_rgba(244,63,94,0.15)]">
|
||||
{node.findings_count} findings
|
||||
<Badge className="h-5 px-2 text-xs bg-rose-100 dark:bg-rose-500/20 text-rose-600 dark:text-rose-300 border border-rose-300 dark:border-rose-500/40 font-mono font-bold">
|
||||
{node.findings_count}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Children with animated reveal */}
|
||||
{/* Children */}
|
||||
{expanded && hasChildren && (
|
||||
<div
|
||||
className="relative animate-in slide-in-from-top-1 duration-300"
|
||||
>
|
||||
{/* Vertical connection line for children with gradient */}
|
||||
<div
|
||||
className="absolute w-px bg-gradient-to-b from-border/60 via-border/40 to-transparent"
|
||||
style={{
|
||||
left: `${(depth + 1) * 20 - 10}px`,
|
||||
top: '0',
|
||||
height: `calc(100% - 24px)`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{node.children.map((child) => (
|
||||
<div className="relative">
|
||||
{node.children.map((child, index) => (
|
||||
<AgentTreeNodeItem
|
||||
key={child.agent_id}
|
||||
node={child}
|
||||
depth={depth + 1}
|
||||
selectedId={selectedId}
|
||||
onSelect={onSelect}
|
||||
isLast={index === node.children.length - 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -85,45 +85,36 @@ export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: L
|
|||
return (
|
||||
<div
|
||||
className={`
|
||||
group relative mb-2 transition-all duration-300 ease-out
|
||||
group relative transition-all duration-300 ease-out
|
||||
${isCollapsible ? 'cursor-pointer' : ''}
|
||||
`}
|
||||
onClick={isCollapsible ? onToggle : undefined}
|
||||
>
|
||||
{/* Main card with enhanced styling */}
|
||||
{/* Main card */}
|
||||
<div className={`
|
||||
relative rounded-lg border-l-3 overflow-hidden backdrop-blur-sm
|
||||
relative rounded-lg border-l-3 overflow-hidden
|
||||
${config.borderColor}
|
||||
${isExpanded ? 'bg-card/80 shadow-lg' : 'bg-card/40'}
|
||||
${isCollapsible ? 'hover:bg-card/60 hover:shadow-md' : ''}
|
||||
${isFinding ? 'border border-rose-500/20 shadow-[0_0_15px_rgba(244,63,94,0.1)]' : 'border border-transparent'}
|
||||
${isError ? 'border border-red-500/20 shadow-[0_0_15px_rgba(239,68,68,0.1)]' : ''}
|
||||
${isDispatch ? 'border border-sky-500/20' : ''}
|
||||
transition-all duration-300
|
||||
${isExpanded ? 'bg-slate-100 dark:bg-card/80' : 'bg-slate-50 dark:bg-card/40'}
|
||||
${isCollapsible ? 'hover:bg-slate-100 dark:hover:bg-card/60' : ''}
|
||||
${isFinding ? 'border border-rose-500/30 dark:border-rose-500/20 !bg-rose-50 dark:!bg-rose-950/20' : 'border border-slate-200 dark:border-transparent'}
|
||||
${isError ? 'border border-red-500/30 dark:border-red-500/20 !bg-red-50 dark:!bg-red-950/20' : ''}
|
||||
${isDispatch ? 'border-sky-500/30 dark:border-sky-500/20 !bg-sky-50 dark:!bg-sky-950/20' : ''}
|
||||
${isThinking ? '!bg-violet-50 dark:!bg-violet-950/20 border-violet-500/30 dark:border-violet-500/20' : ''}
|
||||
${isTool ? '!bg-amber-50 dark:!bg-amber-950/20 border-amber-500/30 dark:border-amber-500/20' : ''}
|
||||
`}>
|
||||
{/* Enhanced gradient overlay */}
|
||||
<div className={`absolute inset-0 opacity-30 pointer-events-none bg-gradient-to-r ${config.bgColor} to-transparent`} />
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative px-4 py-3">
|
||||
{/* Header row */}
|
||||
<div className="flex items-center gap-2.5">
|
||||
{/* Type icon with glow effect */}
|
||||
<div className="relative flex-shrink-0">
|
||||
<div className={`${item.isStreaming ? 'animate-pulse' : ''} transition-transform duration-300 group-hover:scale-110`}>
|
||||
{/* Type icon */}
|
||||
<div className="flex-shrink-0">
|
||||
{config.icon}
|
||||
</div>
|
||||
{item.isStreaming && (
|
||||
<div className="absolute inset-0 blur-md opacity-60">
|
||||
{config.icon}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Type label with enhanced styling */}
|
||||
{/* Type label */}
|
||||
<span className={`
|
||||
text-xs font-mono font-bold uppercase tracking-wider px-2 py-1 rounded-md
|
||||
border transition-all duration-300
|
||||
text-xs font-mono font-bold uppercase tracking-wider px-2 py-1 rounded-md border
|
||||
${isThinking ? 'bg-violet-500/20 text-violet-600 dark:text-violet-300 border-violet-500/30' : ''}
|
||||
${isTool ? 'bg-amber-500/20 text-amber-600 dark:text-amber-300 border-amber-500/30' : ''}
|
||||
${isFinding ? 'bg-rose-500/20 text-rose-600 dark:text-rose-300 border-rose-500/30' : ''}
|
||||
|
|
@ -138,12 +129,12 @@ export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: L
|
|||
{LOG_TYPE_LABELS[item.type] || 'LOG'}
|
||||
</span>
|
||||
|
||||
{/* Timestamp with subtle styling */}
|
||||
<span className="text-xs text-muted-foreground/80 font-mono flex-shrink-0 tabular-nums">
|
||||
{/* Timestamp */}
|
||||
<span className="text-xs text-muted-foreground font-mono flex-shrink-0 tabular-nums">
|
||||
{item.time}
|
||||
</span>
|
||||
|
||||
{/* Separator with animation */}
|
||||
{/* Separator */}
|
||||
<Zap className="w-3 h-3 text-muted-foreground/50 flex-shrink-0" />
|
||||
|
||||
{/* Status icon for info messages */}
|
||||
|
|
@ -151,36 +142,36 @@ export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: L
|
|||
|
||||
{/* Title - for non-thinking types */}
|
||||
{!isThinking && (
|
||||
<span className="text-sm text-foreground font-medium truncate flex-1 leading-relaxed">
|
||||
<span className="text-sm text-foreground font-medium truncate flex-1">
|
||||
{formattedTitle}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Streaming cursor with enhanced animation */}
|
||||
{/* Streaming cursor */}
|
||||
{item.isStreaming && (
|
||||
<span className="w-2 h-5 bg-gradient-to-b from-violet-400 to-violet-600 animate-[blink_1s_ease-in-out_infinite] rounded-sm flex-shrink-0 shadow-[0_0_8px_rgba(139,92,246,0.5)]" />
|
||||
<span className="w-2 h-5 bg-violet-500 rounded-sm flex-shrink-0" />
|
||||
)}
|
||||
|
||||
{/* Tool status with enhanced styling */}
|
||||
{/* Tool status */}
|
||||
{item.tool?.status === 'running' && (
|
||||
<div className="flex items-center gap-2 flex-shrink-0 bg-amber-500/15 px-2.5 py-1 rounded-md border border-amber-500/30">
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin text-amber-400" />
|
||||
<span className="text-xs text-amber-400 font-mono uppercase font-semibold">Running</span>
|
||||
<Loader2 className="w-3.5 h-3.5 animate-spin text-amber-600 dark:text-amber-400" />
|
||||
<span className="text-xs text-amber-600 dark:text-amber-400 font-mono uppercase font-semibold">Running</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.tool?.status === 'completed' && (
|
||||
<div className="flex items-center gap-1.5 flex-shrink-0 px-2 py-1 rounded-md bg-emerald-500/10 border border-emerald-500/30">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-500" />
|
||||
<span className="text-xs text-emerald-500 font-mono uppercase">Done</span>
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-600 dark:text-emerald-500" />
|
||||
<span className="text-xs text-emerald-600 dark:text-emerald-500 font-mono uppercase">Done</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Agent badge with enhanced styling */}
|
||||
{/* Agent badge */}
|
||||
{item.agentName && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="h-6 px-2.5 text-xs uppercase tracking-wider border-primary/40 text-primary bg-primary/10 flex-shrink-0 font-semibold shadow-[0_0_10px_rgba(255,107,44,0.1)]"
|
||||
className="h-6 px-2.5 text-xs uppercase tracking-wider border-primary/40 text-primary bg-primary/10 flex-shrink-0 font-semibold"
|
||||
>
|
||||
{item.agentName}
|
||||
</Badge>
|
||||
|
|
@ -188,14 +179,14 @@ export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: L
|
|||
|
||||
{/* Right side info */}
|
||||
<div className="flex items-center gap-2.5 flex-shrink-0 ml-auto">
|
||||
{/* Duration badge with enhanced styling */}
|
||||
{/* Duration badge */}
|
||||
{item.tool?.duration !== undefined && (
|
||||
<span className="text-xs text-muted-foreground font-mono bg-muted/80 px-2 py-1 rounded-md border border-border/50 tabular-nums">
|
||||
<span className="text-xs text-muted-foreground font-mono bg-muted px-2 py-1 rounded-md border border-border tabular-nums">
|
||||
{item.tool.duration}ms
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Severity badge with enhanced styling */}
|
||||
{/* Severity badge */}
|
||||
{item.severity && (
|
||||
<Badge
|
||||
className={`
|
||||
|
|
@ -207,53 +198,53 @@ export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: L
|
|||
</Badge>
|
||||
)}
|
||||
|
||||
{/* Expand indicator with enhanced styling */}
|
||||
{/* Expand indicator */}
|
||||
{isCollapsible && (
|
||||
<div className={`
|
||||
w-6 h-6 flex items-center justify-center rounded-md transition-all duration-300
|
||||
${isExpanded ? 'bg-primary/20 border border-primary/30' : 'bg-muted/50 border border-transparent group-hover:border-border/50'}
|
||||
w-6 h-6 flex items-center justify-center rounded-md
|
||||
${isExpanded ? 'bg-primary/20 border border-primary/30' : 'bg-muted border border-border'}
|
||||
`}>
|
||||
{isExpanded ? (
|
||||
<ChevronUp className="w-4 h-4 text-primary" />
|
||||
) : (
|
||||
<ChevronDown className="w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors" />
|
||||
<ChevronDown className="w-4 h-4 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Thinking content - always visible with enhanced styling */}
|
||||
{/* Thinking content - always visible */}
|
||||
{isThinking && item.content && (
|
||||
<div className="mt-3 relative">
|
||||
<div className="absolute left-0 top-0 bottom-0 w-0.5 bg-gradient-to-b from-violet-500/60 via-violet-500/30 to-transparent rounded-full" />
|
||||
<div className="pl-4 text-sm text-foreground/90 leading-relaxed whitespace-pre-wrap break-words">
|
||||
<div className="absolute left-0 top-0 bottom-0 w-0.5 bg-violet-500/50 rounded-full" />
|
||||
<div className="pl-4 text-sm text-foreground/90 whitespace-pre-wrap break-words">
|
||||
{item.content}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Collapsible content with enhanced styling */}
|
||||
{/* Collapsible content */}
|
||||
{!isThinking && showContent && item.content && (
|
||||
<div className="mt-3 overflow-hidden animate-in slide-in-from-top-2 duration-300">
|
||||
<div className="bg-card/80 rounded-lg border border-border/50 overflow-hidden backdrop-blur-sm shadow-inner">
|
||||
{/* Mini header with enhanced styling */}
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b border-border/50 bg-muted/50">
|
||||
<div className="mt-3 overflow-hidden">
|
||||
<div className="bg-card rounded-lg border border-border overflow-hidden">
|
||||
{/* Mini header */}
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b border-border bg-muted/50">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1.5 h-1.5 rounded-full bg-muted-foreground/50" />
|
||||
<span className="text-xs text-muted-foreground font-mono uppercase tracking-wider">
|
||||
<span className="text-xs text-muted-foreground font-mono uppercase">
|
||||
{isTool ? 'Output' : 'Details'}
|
||||
</span>
|
||||
</div>
|
||||
{item.tool?.status === 'completed' && (
|
||||
<div className="flex items-center gap-1.5 px-2 py-0.5 rounded bg-emerald-500/10 border border-emerald-500/20">
|
||||
<CheckCircle2 className="w-3 h-3 text-emerald-500" />
|
||||
<span className="text-xs text-emerald-500 font-mono">Complete</span>
|
||||
<CheckCircle2 className="w-3 h-3 text-emerald-600 dark:text-emerald-500" />
|
||||
<span className="text-xs text-emerald-600 dark:text-emerald-500 font-mono">Complete</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Content with enhanced styling */}
|
||||
<pre className="p-4 text-sm font-mono text-foreground/85 max-h-64 overflow-y-auto custom-scrollbar whitespace-pre-wrap break-words leading-relaxed bg-gradient-to-b from-transparent to-muted/20">
|
||||
{/* Content */}
|
||||
<pre className="p-4 text-sm font-mono text-foreground/85 max-h-64 overflow-y-auto custom-scrollbar whitespace-pre-wrap break-words">
|
||||
{item.content}
|
||||
</pre>
|
||||
</div>
|
||||
|
|
@ -262,13 +253,6 @@ export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: L
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Inline styles for cursor blink */}
|
||||
<style>{`
|
||||
@keyframes blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0; }
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -772,17 +772,6 @@ function AgentAuditPageContent() {
|
|||
|
||||
return (
|
||||
<div className="h-screen bg-background flex flex-col overflow-hidden relative">
|
||||
{/* Animated gradient background */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-background via-background to-primary/5 pointer-events-none" />
|
||||
{/* Subtle grid background with animation */}
|
||||
<div className="absolute inset-0 cyber-grid-subtle opacity-30 pointer-events-none animate-grid-pulse" />
|
||||
{/* Scanline effect */}
|
||||
<div className="absolute inset-0 scanline-overlay pointer-events-none opacity-30" />
|
||||
{/* Corner accent lines */}
|
||||
<div className="absolute top-0 left-0 w-32 h-px bg-gradient-to-r from-primary/50 to-transparent pointer-events-none" />
|
||||
<div className="absolute top-0 left-0 w-px h-32 bg-gradient-to-b from-primary/50 to-transparent pointer-events-none" />
|
||||
<div className="absolute bottom-0 right-0 w-32 h-px bg-gradient-to-l from-primary/50 to-transparent pointer-events-none" />
|
||||
<div className="absolute bottom-0 right-0 w-px h-32 bg-gradient-to-t from-primary/50 to-transparent pointer-events-none" />
|
||||
|
||||
{/* Header */}
|
||||
<Header
|
||||
|
|
@ -797,33 +786,21 @@ function AgentAuditPageContent() {
|
|||
{/* Main content */}
|
||||
<div className="flex-1 flex overflow-hidden relative">
|
||||
{/* Left Panel - Activity Log */}
|
||||
<div className="w-3/4 flex flex-col border-r border-border/50 relative">
|
||||
{/* Panel glow effect */}
|
||||
<div className="absolute inset-y-0 right-0 w-px bg-gradient-to-b from-transparent via-primary/20 to-transparent pointer-events-none" />
|
||||
<div className="w-3/4 flex flex-col border-r border-border relative">
|
||||
{/* Log header */}
|
||||
<div className="flex-shrink-0 h-12 border-b border-border/50 flex items-center justify-between px-5 bg-card/80 backdrop-blur-md relative overflow-hidden">
|
||||
{/* Header accent line */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-primary/30 via-transparent to-primary/30" />
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground relative z-10">
|
||||
<div className="flex-shrink-0 h-12 border-b border-border flex items-center justify-between px-5 bg-card">
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="relative">
|
||||
<Terminal className="w-4 h-4 text-primary" />
|
||||
{isConnected && (
|
||||
<div className="absolute -top-0.5 -right-0.5 w-1.5 h-1.5 bg-emerald-400 rounded-full animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
<span className="uppercase font-bold tracking-[0.12em] text-foreground text-sm">Activity Log</span>
|
||||
<span className="uppercase font-bold tracking-wider text-foreground text-sm">Activity Log</span>
|
||||
</div>
|
||||
{isConnected && (
|
||||
<div className="flex items-center gap-2 px-2.5 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/30">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400 shadow-[0_0_10px_rgba(52,211,153,0.6)]"></span>
|
||||
</span>
|
||||
<span className="text-xs font-mono uppercase tracking-wider text-emerald-400 font-semibold">Live</span>
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500"></span>
|
||||
<span className="text-xs font-mono uppercase tracking-wider text-emerald-600 dark:text-emerald-400 font-semibold">Live</span>
|
||||
</div>
|
||||
)}
|
||||
<Badge variant="outline" className="h-6 px-2 text-xs border-border/60 text-muted-foreground font-mono bg-muted/50 backdrop-blur-sm">
|
||||
<Badge variant="outline" className="h-6 px-2 text-xs border-border text-muted-foreground font-mono bg-muted">
|
||||
{filteredLogs.length}{!showAllLogs && logs.length !== filteredLogs.length ? ` / ${logs.length}` : ''} entries
|
||||
</Badge>
|
||||
</div>
|
||||
|
|
@ -831,36 +808,30 @@ function AgentAuditPageContent() {
|
|||
<button
|
||||
onClick={() => setAutoScroll(!isAutoScroll)}
|
||||
className={`
|
||||
relative flex items-center gap-2 text-xs px-3 py-1.5 rounded-md font-mono uppercase tracking-wider
|
||||
transition-all duration-300 ease-out overflow-hidden
|
||||
flex items-center gap-2 text-xs px-3 py-1.5 rounded-md font-mono uppercase tracking-wider
|
||||
${isAutoScroll
|
||||
? 'bg-primary/15 text-primary border border-primary/50 shadow-[0_0_15px_rgba(255,95,31,0.2)]'
|
||||
: 'text-muted-foreground hover:text-foreground border border-border/50 hover:border-border hover:bg-muted/30'
|
||||
? 'bg-primary/15 text-primary border border-primary/50'
|
||||
: 'text-muted-foreground hover:text-foreground border border-border hover:bg-muted'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{isAutoScroll && (
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-primary/10 via-primary/5 to-primary/10 animate-shimmer" />
|
||||
)}
|
||||
<ArrowDown className={`w-3.5 h-3.5 relative z-10 transition-transform duration-300 ${isAutoScroll ? 'animate-bounce' : ''}`} />
|
||||
<span className="relative z-10">Auto-scroll</span>
|
||||
<ArrowDown className="w-3.5 h-3.5" />
|
||||
<span>Auto-scroll</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Log content */}
|
||||
<div className="flex-1 overflow-y-auto p-5 custom-scrollbar bg-gradient-to-b from-background/80 to-background/40">
|
||||
<div className="flex-1 overflow-y-auto p-5 custom-scrollbar bg-muted/30">
|
||||
{/* Filter indicator */}
|
||||
{selectedAgentId && !showAllLogs && (
|
||||
<div className="mb-4 px-4 py-2.5 bg-primary/10 border border-primary/30 rounded-lg flex items-center justify-between backdrop-blur-sm shadow-[0_0_20px_rgba(255,107,44,0.1)]">
|
||||
<div className="mb-4 px-4 py-2.5 bg-primary/10 border border-primary/30 rounded-lg flex items-center justify-between">
|
||||
<div className="flex items-center gap-2.5 text-sm text-primary">
|
||||
<div className="p-1.5 rounded bg-primary/20">
|
||||
<Filter className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
<span className="tracking-wide font-medium">Filtering logs for selected agent</span>
|
||||
<span className="font-medium">Filtering logs for selected agent</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => selectAgent(null)}
|
||||
className="text-xs text-muted-foreground hover:text-primary transition-colors font-mono uppercase tracking-wider px-2 py-1 rounded hover:bg-primary/10"
|
||||
className="text-xs text-muted-foreground hover:text-primary font-mono uppercase px-2 py-1 rounded hover:bg-primary/10"
|
||||
>
|
||||
Clear Filter
|
||||
</button>
|
||||
|
|
@ -890,7 +861,7 @@ function AgentAuditPageContent() {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-0.5">
|
||||
<div className="space-y-3">
|
||||
{filteredLogs.map(item => (
|
||||
<LogEntry
|
||||
key={item.id}
|
||||
|
|
@ -906,31 +877,26 @@ function AgentAuditPageContent() {
|
|||
|
||||
{/* Status bar */}
|
||||
{task && (
|
||||
<div className="flex-shrink-0 h-10 border-t border-border/50 flex items-center justify-between px-5 text-xs bg-card/80 backdrop-blur-md relative overflow-hidden">
|
||||
<div className="flex-shrink-0 h-10 border-t border-border flex items-center justify-between px-5 text-xs bg-card relative overflow-hidden">
|
||||
{/* Progress bar background */}
|
||||
<div
|
||||
className="absolute inset-0 bg-gradient-to-r from-primary/10 to-transparent transition-all duration-700 ease-out"
|
||||
className="absolute inset-0 bg-primary/10"
|
||||
style={{ width: `${task.progress_percentage || 0}%` }}
|
||||
/>
|
||||
{/* Top accent line */}
|
||||
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-primary/30 via-transparent to-primary/30" />
|
||||
|
||||
<span className="relative z-10">
|
||||
{isRunning ? (
|
||||
<span className="flex items-center gap-2.5 text-emerald-400">
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.6)]"></span>
|
||||
</span>
|
||||
<span className="font-mono tracking-wide font-semibold">{statusVerb}{'.'.repeat(statusDots)}</span>
|
||||
<span className="flex items-center gap-2.5 text-emerald-600 dark:text-emerald-400">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-500"></span>
|
||||
<span className="font-mono font-semibold">{statusVerb}{'.'.repeat(statusDots)}</span>
|
||||
</span>
|
||||
) : isComplete ? (
|
||||
<span className="flex items-center gap-2 text-muted-foreground font-mono tracking-wide">
|
||||
<span className="flex items-center gap-2 text-muted-foreground font-mono">
|
||||
<span className={`w-2 h-2 rounded-full ${task.status === 'completed' ? 'bg-emerald-500' : task.status === 'failed' ? 'bg-rose-500' : 'bg-amber-500'}`} />
|
||||
AUDIT {task.status?.toUpperCase()}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground font-mono tracking-wide">READY</span>
|
||||
<span className="text-muted-foreground font-mono">READY</span>
|
||||
)}
|
||||
</span>
|
||||
<div className="flex items-center gap-5 font-mono text-muted-foreground relative z-10">
|
||||
|
|
@ -938,16 +904,16 @@ function AgentAuditPageContent() {
|
|||
<span className="text-primary font-bold text-sm">{task.progress_percentage?.toFixed(0) || 0}</span>
|
||||
<span className="text-muted-foreground text-xs">%</span>
|
||||
</div>
|
||||
<div className="w-px h-4 bg-border/50" />
|
||||
<div className="w-px h-4 bg-border" />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-foreground font-semibold">{task.analyzed_files}</span>
|
||||
<span className="text-muted-foreground">/ {task.total_files}</span>
|
||||
<span className="text-muted-foreground/70 text-xs">files</span>
|
||||
<span className="text-muted-foreground text-xs">files</span>
|
||||
</div>
|
||||
<div className="w-px h-4 bg-border/50" />
|
||||
<div className="w-px h-4 bg-border" />
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-foreground font-semibold">{task.tool_calls_count || 0}</span>
|
||||
<span className="text-muted-foreground/70 text-xs">tools</span>
|
||||
<span className="text-muted-foreground text-xs">tools</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -955,55 +921,50 @@ function AgentAuditPageContent() {
|
|||
</div>
|
||||
|
||||
{/* Right Panel - Agent Tree + Stats */}
|
||||
<div className="w-1/4 flex flex-col bg-gradient-to-b from-background to-background/95 relative">
|
||||
{/* Panel left glow */}
|
||||
<div className="absolute inset-y-0 left-0 w-px bg-gradient-to-b from-transparent via-primary/10 to-transparent pointer-events-none" />
|
||||
|
||||
<div className="w-1/4 flex flex-col bg-background relative">
|
||||
{/* Agent Tree section */}
|
||||
<div className="flex-1 flex flex-col border-b border-border/50 overflow-hidden">
|
||||
<div className="flex-1 flex flex-col border-b border-border overflow-hidden">
|
||||
{/* Tree header */}
|
||||
<div className="flex-shrink-0 h-12 border-b border-border/50 flex items-center justify-between px-4 bg-card/80 backdrop-blur-md relative overflow-hidden">
|
||||
{/* Header accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-violet-500/30 via-transparent to-violet-500/30" />
|
||||
|
||||
<div className="flex items-center gap-2.5 text-xs text-muted-foreground relative z-10">
|
||||
<div className="relative">
|
||||
<Bot className="w-4 h-4 text-violet-500" />
|
||||
{agentTree && agentTree.running_agents > 0 && (
|
||||
<div className="absolute -top-0.5 -right-0.5 w-1.5 h-1.5 bg-emerald-400 rounded-full animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
<span className="uppercase font-bold tracking-[0.12em] text-foreground text-sm">Agent Tree</span>
|
||||
{agentTree && (
|
||||
<Badge variant="outline" className="h-5 px-2 text-xs border-violet-500/30 text-violet-500 font-mono bg-violet-500/10">
|
||||
<div className="flex-shrink-0 h-12 border-b border-border flex items-center justify-between px-4 bg-card">
|
||||
<div className="flex items-center gap-2.5 text-xs text-muted-foreground">
|
||||
<Bot className="w-4 h-4 text-violet-600 dark:text-violet-500" />
|
||||
<span className="uppercase font-bold tracking-wider text-foreground text-sm">
|
||||
{selectedAgentId && !showAllLogs ? 'Agent Detail' : 'Agent Tree'}
|
||||
</span>
|
||||
{!selectedAgentId && agentTree && (
|
||||
<Badge variant="outline" className="h-5 px-2 text-xs border-violet-500/30 text-violet-600 dark:text-violet-500 font-mono bg-violet-500/10">
|
||||
{agentTree.total_agents}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 relative z-10">
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedAgentId && !showAllLogs && (
|
||||
<button
|
||||
onClick={() => selectAgent(null)}
|
||||
className="text-xs text-primary hover:text-primary/80 transition-colors font-mono uppercase tracking-wider px-2 py-1 rounded hover:bg-primary/10"
|
||||
className="text-xs text-primary hover:text-primary/80 font-mono uppercase px-2 py-1 rounded hover:bg-primary/10"
|
||||
>
|
||||
Show All
|
||||
Back
|
||||
</button>
|
||||
)}
|
||||
{agentTree && agentTree.running_agents > 0 && (
|
||||
{!selectedAgentId && agentTree && agentTree.running_agents > 0 && (
|
||||
<div className="flex items-center gap-1.5 px-2 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/30">
|
||||
<span className="relative flex h-1.5 w-1.5">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.5)]"></span>
|
||||
</span>
|
||||
<span className="text-xs font-mono text-emerald-400 font-semibold">{agentTree.running_agents}</span>
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
|
||||
<span className="text-xs font-mono text-emerald-600 dark:text-emerald-400 font-semibold">{agentTree.running_agents}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tree content */}
|
||||
<div className="flex-1 overflow-y-auto p-3 custom-scrollbar bg-gradient-to-b from-background/60 to-background/30">
|
||||
{treeNodes.length > 0 ? (
|
||||
{/* Tree content or Agent Detail */}
|
||||
<div className="flex-1 overflow-y-auto p-3 custom-scrollbar bg-muted/20">
|
||||
{selectedAgentId && !showAllLogs ? (
|
||||
/* Agent Detail Panel - 覆盖整个内容区域 */
|
||||
<AgentDetailPanel
|
||||
agentId={selectedAgentId}
|
||||
treeNodes={treeNodes}
|
||||
onClose={() => selectAgent(null)}
|
||||
/>
|
||||
) : treeNodes.length > 0 ? (
|
||||
<div className="space-y-0.5">
|
||||
{treeNodes.map(node => (
|
||||
<AgentTreeNodeItem
|
||||
|
|
@ -1018,18 +979,13 @@ function AgentAuditPageContent() {
|
|||
<div className="h-full flex items-center justify-center text-muted-foreground text-xs">
|
||||
{isRunning ? (
|
||||
<div className="flex flex-col items-center gap-3 p-6">
|
||||
<div className="relative">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-violet-500" />
|
||||
<div className="absolute inset-0 blur-md opacity-50">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-violet-500" />
|
||||
</div>
|
||||
</div>
|
||||
<span className="font-mono tracking-wide text-center">INITIALIZING<br/>AGENTS...</span>
|
||||
<Loader2 className="w-6 h-6 animate-spin text-violet-600 dark:text-violet-500" />
|
||||
<span className="font-mono text-center">INITIALIZING<br/>AGENTS...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-2 p-6 text-center">
|
||||
<Bot className="w-8 h-8 text-muted-foreground/50" />
|
||||
<span className="font-mono tracking-wide">NO AGENTS YET</span>
|
||||
<span className="font-mono">NO AGENTS YET</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -1037,18 +993,8 @@ function AgentAuditPageContent() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom section - Details + Stats */}
|
||||
<div className="flex-shrink-0 p-4 space-y-3 bg-card/50 backdrop-blur-sm">
|
||||
{/* Agent detail panel */}
|
||||
{selectedAgentId && !showAllLogs && (
|
||||
<AgentDetailPanel
|
||||
agentId={selectedAgentId}
|
||||
treeNodes={treeNodes}
|
||||
onClose={() => selectAgent(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Stats panel */}
|
||||
{/* Bottom section - Stats */}
|
||||
<div className="flex-shrink-0 p-4 bg-card">
|
||||
<StatsPanel task={task} findings={findings} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue