feat(前端): 增强UI组件视觉效果和交互体验

refactor(认证): 支持记住我功能并优化token存储逻辑

style(字体): 更新字体配置增加CJK回退支持

perf(全局样式): 添加新动画效果和视觉增强样式

fix(AlertDialog): 修复portal容器定位问题
This commit is contained in:
lintsinghua 2025-12-18 20:47:11 +08:00
parent 8ee98a20eb
commit 3a9dcdbc7a
13 changed files with 1180 additions and 590 deletions

View File

@ -5,11 +5,20 @@
@font-face {
font-family: 'ArkPixel';
src: url('/fonts/ark-pixel-12px-monospaced-zh_cn.ttf.woff2') format('woff2');
font-weight: normal;
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
/* CJK fallback font with consistent weight */
@font-face {
font-family: 'CJK Fallback';
src: local('PingFang SC'), local('Microsoft YaHei'), local('Noto Sans SC'), local('Hiragino Sans GB');
font-weight: 100 900;
font-style: normal;
unicode-range: U+4E00-9FFF, U+3400-4DBF, U+F900-FAFF, U+FE30-FE4F;
}
/*
DeepAudit Design System v3.1
Theme System with CSS Variables
@ -729,51 +738,53 @@
to { opacity: 1; transform: translateY(0); }
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
opacity: 0.3;
}
25% {
transform: translateY(-20px) translateX(10px);
opacity: 0.6;
}
50% {
transform: translateY(-40px) translateX(-5px);
opacity: 0.3;
}
75% {
transform: translateY(-20px) translateX(-10px);
opacity: 0.5;
}
}
@keyframes grid-pulse {
0%, 100% { opacity: 0.03; }
50% { opacity: 0.05; }
}
@keyframes glow-pulse {
0%, 100% { box-shadow: 0 0 20px rgba(255, 107, 44, 0.2); }
50% { box-shadow: 0 0 30px rgba(255, 107, 44, 0.4); }
}
@keyframes border-glow {
0%, 100% { border-color: rgba(255, 107, 44, 0.3); }
50% { border-color: rgba(255, 107, 44, 0.6); }
}
.animate-pulse-glow { animation: pulse-glow 2s ease-in-out infinite; }
.animate-blink { animation: blink 1s step-end infinite; }
.animate-fade-slide-in { animation: fadeSlideIn 0.3s ease-out forwards; }
/* ============ Corner Decorations ============ */
.corner-decoration {
position: absolute;
font-size: 11px;
font-family: monospace;
z-index: 30;
color: var(--cyber-text-muted);
letter-spacing: 0.1em;
}
.corner-tl { top: 0.75rem; left: 0.75rem; }
.corner-tr { top: 0.75rem; right: 0.75rem; text-align: right; }
.corner-bl { bottom: 0.75rem; left: 0.75rem; }
.corner-br { bottom: 0.75rem; right: 0.75rem; text-align: right; }
/* ============ HUD Elements ============ */
.hud-border {
position: relative;
}
.hud-border::before,
.hud-border::after {
content: '';
position: absolute;
width: 10px;
height: 10px;
}
.hud-border::before {
top: 0;
left: 0;
border-top: 2px solid var(--cyber-border-accent);
border-left: 2px solid var(--cyber-border-accent);
}
.hud-border::after {
bottom: 0;
right: 0;
border-bottom: 2px solid var(--cyber-border-accent);
border-right: 2px solid var(--cyber-border-accent);
}
.animate-shimmer { animation: shimmer 2s ease-in-out infinite; }
.animate-float { animation: float 10s ease-in-out infinite; }
.animate-grid-pulse { animation: grid-pulse 4s ease-in-out infinite; }
.animate-glow-pulse { animation: glow-pulse 2s ease-in-out infinite; }
.animate-border-glow { animation: border-glow 2s ease-in-out infinite; }
/* ============ Data Display ============ */
.data-value {
@ -917,4 +928,114 @@
border-top: 1px solid var(--cyber-border);
background: var(--cyber-bg-elevated);
}
/* ============ Enhanced Visual Effects ============ */
/* Scanline overlay effect */
.scanline-overlay {
background-image: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.03) 2px,
rgba(0, 0, 0, 0.03) 4px
);
pointer-events: none;
}
/* Text glow effects */
.text-glow-primary {
text-shadow: 0 0 10px rgba(255, 107, 44, 0.5), 0 0 20px rgba(255, 107, 44, 0.3);
}
.text-glow-success {
text-shadow: 0 0 10px rgba(52, 211, 153, 0.5), 0 0 20px rgba(52, 211, 153, 0.3);
}
.text-glow-cyan {
text-shadow: 0 0 10px rgba(6, 182, 212, 0.5), 0 0 20px rgba(6, 182, 212, 0.3);
}
/* Glass morphism effect */
.glass {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.dark .glass {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.05);
}
/* Gradient borders */
.gradient-border {
position: relative;
}
.gradient-border::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(135deg, rgba(255, 107, 44, 0.5), transparent, rgba(255, 107, 44, 0.5));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
}
/* Hover lift effect */
.hover-lift {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.hover-lift:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
/* Border left accent with gradient */
.border-l-3 {
border-left-width: 3px;
}
/* Smooth transitions for interactive elements */
.interactive {
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Focus ring enhancement */
.focus-ring:focus-visible {
outline: none;
box-shadow: 0 0 0 2px var(--cyber-bg), 0 0 0 4px hsl(var(--primary) / 0.5);
}
/* Subtle background patterns */
.pattern-dots {
background-image: radial-gradient(circle, var(--cyber-border) 1px, transparent 1px);
background-size: 20px 20px;
}
/* Animated underline */
.animated-underline {
position: relative;
}
.animated-underline::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 0;
height: 2px;
background: linear-gradient(90deg, hsl(var(--primary)), hsl(var(--primary) / 0.5));
transition: width 0.3s ease;
}
.animated-underline:hover::after {
width: 100%;
}
}

View File

@ -1,6 +1,6 @@
/**
* Sidebar Component
* Cyberpunk Terminal Aesthetic
* Premium Terminal Aesthetic with Enhanced Visual Design
*/
import { useState } from "react";
@ -23,21 +23,22 @@ import {
Shield,
MessageSquare,
Bot,
ExternalLink,
} from "lucide-react";
import routes from "@/app/routes";
import { version } from "../../../package.json";
// Icon mapping for routes
// Icon mapping for routes with consistent sizing
const routeIcons: Record<string, React.ReactNode> = {
"/": <Bot className="w-6 h-6" />,
"/dashboard": <LayoutDashboard className="w-6 h-6" />,
"/projects": <FolderGit2 className="w-6 h-6" />,
"/instant-analysis": <Zap className="w-6 h-6" />,
"/audit-tasks": <ListTodo className="w-6 h-6" />,
"/audit-rules": <Shield className="w-6 h-6" />,
"/prompts": <MessageSquare className="w-6 h-6" />,
"/admin": <Settings className="w-6 h-6" />,
"/recycle-bin": <Trash2 className="w-6 h-6" />,
"/": <Bot className="w-5 h-5" />,
"/dashboard": <LayoutDashboard className="w-5 h-5" />,
"/projects": <FolderGit2 className="w-5 h-5" />,
"/instant-analysis": <Zap className="w-5 h-5" />,
"/audit-tasks": <ListTodo className="w-5 h-5" />,
"/audit-rules": <Shield className="w-5 h-5" />,
"/prompts": <MessageSquare className="w-5 h-5" />,
"/admin": <Settings className="w-5 h-5" />,
"/recycle-bin": <Trash2 className="w-5 h-5" />,
};
interface SidebarProps {
@ -89,65 +90,77 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
}}
>
<div className="flex flex-col h-full relative">
{/* Subtle gradient background */}
<div className="absolute inset-0 bg-gradient-to-b from-primary/5 via-transparent to-transparent pointer-events-none" />
{/* Subtle grid background */}
<div
className="absolute inset-0 opacity-30 pointer-events-none"
className="absolute inset-0 opacity-20 pointer-events-none"
style={{
backgroundImage: `
linear-gradient(var(--cyber-border-accent) 1px, transparent 1px),
linear-gradient(90deg, var(--cyber-border-accent) 1px, transparent 1px)
`,
backgroundSize: '24px 24px',
backgroundSize: '32px 32px',
}}
/>
{/* Logo Section */}
{/* Right edge glow */}
<div className="absolute top-0 right-0 bottom-0 w-px bg-gradient-to-b from-primary/30 via-primary/10 to-primary/30 pointer-events-none" />
{/* Logo Section with enhanced styling */}
<div
className={`relative flex items-center h-[72px] ${collapsed ? 'px-3 justify-center' : 'px-4 pr-6'}`}
className={`relative flex items-center h-[72px] ${collapsed ? 'px-3 justify-center' : 'px-5 pr-6'}`}
style={{
background: 'var(--cyber-bg-elevated)',
borderBottom: '1px solid var(--cyber-border)'
}}
>
{/* Bottom accent line */}
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-primary/40 via-primary/20 to-transparent" />
<Link
to="/"
className={`flex items-center gap-3 group transition-all duration-300 ${collapsed ? 'justify-center' : 'flex-1 min-w-0'}`}
onClick={() => setMobileOpen(false)}
>
{/* Logo Icon */}
{/* Logo Icon with enhanced styling */}
<div className="relative flex-shrink-0">
<div
className="w-10 h-10 rounded-lg flex items-center justify-center overflow-hidden group-hover:border-primary/60 transition-colors"
className="w-11 h-11 rounded-xl flex items-center justify-center overflow-hidden transition-all duration-300 group-hover:shadow-[0_0_20px_rgba(255,107,44,0.3)]"
style={{
background: 'var(--cyber-bg)',
border: '1px solid hsl(var(--primary) / 0.3)'
background: 'linear-gradient(135deg, hsl(var(--primary) / 0.15), hsl(var(--primary) / 0.05))',
border: '1px solid hsl(var(--primary) / 0.4)'
}}
>
<img
src="/logo_deepaudit.png"
alt="DeepAudit"
className="w-7 h-7 object-contain"
className="w-7 h-7 object-contain transition-transform duration-300 group-hover:scale-110"
/>
</div>
{/* Glow effect */}
<div className="absolute inset-0 bg-primary/20 rounded-lg blur-xl opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="absolute inset-0 bg-primary/30 rounded-xl blur-xl opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
</div>
{/* Logo Text */}
{/* Logo Text with enhanced styling */}
<div className={`transition-all duration-300 ${collapsed ? 'w-0 opacity-0 overflow-hidden' : 'flex-1 min-w-0 opacity-100'}`}>
<div
className="text-xl font-bold tracking-wider font-mono"
style={{ textShadow: '0 0 20px rgba(255,107,44,0.3)' }}
className="text-xl font-bold tracking-wider font-mono leading-tight"
style={{ textShadow: '0 0 25px rgba(255,107,44,0.4)' }}
>
<span className="text-primary">DEEP</span>
<span style={{ color: 'var(--cyber-text)' }}>AUDIT</span>
</div>
<div className="text-[10px] text-muted-foreground tracking-[0.15em] uppercase mt-0.5">
Security Agent
</div>
</div>
</Link>
{/* Collapse button */}
{/* Collapse button with enhanced styling */}
<button
className="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded items-center justify-center hover:bg-primary hover:border-primary hover:text-foreground transition-all duration-200"
className="hidden md:flex absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-md items-center justify-center hover:bg-primary hover:border-primary hover:text-white transition-all duration-300 shadow-sm"
style={{
background: 'var(--cyber-bg)',
border: '1px solid var(--cyber-border)',
@ -157,64 +170,69 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
onClick={() => setCollapsed(!collapsed)}
>
{collapsed ? (
<ChevronRight className="w-3 h-3" />
<ChevronRight className="w-3.5 h-3.5" />
) : (
<ChevronLeft className="w-3 h-3" />
<ChevronLeft className="w-3.5 h-3.5" />
)}
</button>
</div>
{/* Navigation */}
<nav className="flex-1 overflow-y-auto py-4 px-3 custom-scrollbar">
<div className="space-y-1">
{/* Navigation with enhanced styling */}
<nav className="flex-1 overflow-y-auto py-4 px-3 custom-scrollbar relative">
<div className="space-y-1.5">
{visibleRoutes.map((route) => {
const isActive = location.pathname === route.path;
return (
<Link
key={route.path}
to={route.path}
className="flex items-center gap-3 px-3 py-2.5 transition-all duration-200 group relative rounded-lg"
className={`
flex items-center gap-3 px-3 py-2.5 transition-all duration-300 group relative rounded-lg
${isActive
? 'bg-primary/15 border border-primary/40 shadow-[0_0_15px_rgba(255,107,44,0.1)]'
: 'border border-transparent hover:bg-card/60 hover:border-border/50'
}
`}
style={{
background: isActive ? 'hsl(var(--primary) / 0.15)' : 'transparent',
border: isActive ? '1px solid hsl(var(--primary) / 0.3)' : '1px solid transparent',
color: isActive ? 'hsl(var(--primary))' : 'var(--cyber-text-muted)'
}}
onClick={() => setMobileOpen(false)}
title={collapsed ? route.name : undefined}
onMouseEnter={(e) => {
if (!isActive) {
e.currentTarget.style.background = 'var(--cyber-hover-bg)';
e.currentTarget.style.color = 'var(--cyber-text)';
}
}}
onMouseLeave={(e) => {
if (!isActive) {
e.currentTarget.style.background = 'transparent';
e.currentTarget.style.color = 'var(--cyber-text-muted)';
}
}}
>
{/* Active indicator */}
{/* Active indicator with glow */}
{isActive && (
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 bg-primary rounded-r" />
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-7 bg-primary rounded-r shadow-[0_0_8px_rgba(255,107,44,0.5)]" />
)}
{/* Icon */}
<span className="flex-shrink-0 transition-colors duration-200">
{routeIcons[route.path] || <LayoutDashboard className="w-6 h-6" />}
{/* Icon with background on active */}
<span className={`
flex-shrink-0 transition-all duration-300 p-1.5 rounded-md
${isActive ? 'bg-primary/20' : 'group-hover:bg-muted/50'}
`}>
{routeIcons[route.path] || <LayoutDashboard className="w-5 h-5" />}
</span>
{/* Label */}
{!collapsed && (
<span className={`font-mono text-base tracking-wide ${isActive ? 'font-semibold' : 'font-medium'}`}>
<span className={`font-mono text-sm tracking-wide transition-all duration-300 ${isActive ? 'font-semibold' : 'font-medium'}`}>
{route.name}
</span>
)}
{/* Hover arrow */}
{/* Hover indicator */}
{!isActive && !collapsed && (
<span className="absolute right-3 opacity-0 group-hover:opacity-100 text-xs text-primary transition-opacity">
<span className="absolute right-3 opacity-0 group-hover:opacity-100 transition-all duration-300 group-hover:translate-x-1">
<ChevronRight className="w-4 h-4 text-primary" />
</span>
)}
</Link>
@ -223,70 +241,97 @@ export default function Sidebar({ collapsed, setCollapsed }: SidebarProps) {
</div>
</nav>
{/* Footer */}
{/* Footer with enhanced styling */}
<div
className="p-3 space-y-1"
className="p-3 space-y-1.5 relative"
style={{
background: 'var(--cyber-bg-elevated)',
borderTop: '1px solid var(--cyber-border)'
}}
>
{/* Top accent line */}
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-primary/20 to-transparent" />
{/* Theme Toggle */}
<ThemeToggle collapsed={collapsed} />
{/* Account Link */}
{/* Account Link with enhanced styling */}
<Link
to="/account"
className="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 group"
className={`
flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-300 group
${location.pathname === '/account'
? 'bg-primary/15 border border-primary/40'
: 'border border-transparent hover:bg-card/60 hover:border-border/50'
}
`}
style={{
background: location.pathname === '/account' ? 'hsl(var(--primary) / 0.15)' : 'transparent',
border: location.pathname === '/account' ? '1px solid hsl(var(--primary) / 0.3)' : '1px solid transparent',
color: location.pathname === '/account' ? 'hsl(var(--primary))' : 'var(--cyber-text-muted)'
}}
onClick={() => setMobileOpen(false)}
title={collapsed ? "账号管理" : undefined}
>
<UserCircle className="w-6 h-6 flex-shrink-0" />
<span className={`p-1.5 rounded-md transition-all duration-300 ${location.pathname === '/account' ? 'bg-primary/20' : 'group-hover:bg-muted/50'}`}>
<UserCircle className="w-5 h-5 flex-shrink-0" />
</span>
{!collapsed && (
<span className="font-mono text-base"></span>
<span className="font-mono text-sm"></span>
)}
</Link>
{/* GitHub Link */}
{/* GitHub Link with enhanced styling */}
<a
href="https://github.com/lintsinghua/DeepAudit"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 group"
className="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-300 group border border-transparent hover:bg-card/60 hover:border-border/50"
style={{ color: 'var(--cyber-text-muted)' }}
title={collapsed ? "GitHub" : undefined}
>
<Github className="w-6 h-6 flex-shrink-0" />
<span className="p-1.5 rounded-md transition-all duration-300 group-hover:bg-muted/50">
<Github className="w-5 h-5 flex-shrink-0" />
</span>
{!collapsed && (
<div className="flex-1 flex items-center justify-between">
<div className="flex flex-col">
<span className="font-mono text-base">GitHub</span>
<span className="text-sm font-mono" style={{ color: 'var(--cyber-text-muted)' }}>v{version}</span>
<span className="font-mono text-sm">GitHub</span>
<span className="text-xs font-mono text-muted-foreground/70">v{version}</span>
</div>
<ExternalLink className="w-3.5 h-3.5 opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground" />
</div>
)}
</a>
{/* System Status */}
{/* System Status with enhanced styling */}
{!collapsed && (
<div className="mt-3 pt-3" style={{ borderTop: '1px solid var(--cyber-border)' }}>
<div className="flex items-center gap-2 px-3 py-2">
<div className="mt-3 pt-3 relative" style={{ borderTop: '1px solid var(--cyber-border)' }}>
<div className="flex items-center gap-2.5 px-3 py-2 rounded-lg bg-emerald-500/10 border border-emerald-500/20">
<div className="relative">
<div
className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"
style={{ boxShadow: '0 0 8px rgba(52, 211, 153, 0.5)' }}
className="w-2.5 h-2.5 rounded-full bg-emerald-400"
style={{ boxShadow: '0 0 10px rgba(52, 211, 153, 0.6)' }}
/>
<span
className="text-sm font-mono uppercase tracking-wider"
style={{ color: 'var(--cyber-text-muted)' }}
>
<div className="absolute inset-0 w-2.5 h-2.5 rounded-full bg-emerald-400 animate-ping opacity-50" />
</div>
<span className="text-xs font-mono uppercase tracking-wider text-emerald-500">
System Online
</span>
</div>
</div>
)}
{/* Collapsed system status indicator */}
{collapsed && (
<div className="flex justify-center py-2">
<div className="relative">
<div
className="w-2.5 h-2.5 rounded-full bg-emerald-400"
style={{ boxShadow: '0 0 10px rgba(52, 211, 153, 0.6)' }}
/>
<div className="absolute inset-0 w-2.5 h-2.5 rounded-full bg-emerald-400 animate-ping opacity-50" />
</div>
</div>
)}
</div>
</div>
</aside>

View File

@ -22,7 +22,7 @@ function AlertDialogPortal({
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" container={document.body} {...props} />
);
}
@ -49,14 +49,16 @@ function AlertDialogContent({
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<AlertDialogPrimitive.Content
data-slot="alert-dialog-content"
className={cn(
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-6 rounded-sm border border-border p-0 shadow-lg duration-200 sm:max-w-lg overflow-hidden",
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 grid w-full max-w-lg gap-6 rounded-sm border border-border p-0 shadow-lg duration-200 overflow-hidden",
className
)}
{...props}
/>
</div>
</AlertDialogPortal>
);
}

View File

@ -1,30 +1,38 @@
/**
* Agent Tree Node Component
* Elegant tree visualization with cassette futurism aesthetic
* Elegant tree visualization with enhanced visual design
* Features: Animated connection lines, status indicators, smooth transitions
* Enhanced color palette for better visibility
* Premium visual effects with depth and hierarchy
*/
import { useState, memo } from "react";
import { ChevronDown, ChevronRight, Bot, Cpu, Scan, FileSearch, ShieldCheck } from "lucide-react";
import { ChevronDown, ChevronRight, Bot, Cpu, Scan, FileSearch, ShieldCheck, Zap } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { AGENT_STATUS_CONFIG } from "../constants";
import type { AgentTreeNodeItemProps } from "../types";
// Agent type icons with enhanced colors (light/dark mode compatible)
// Agent type icons with enhanced colors and styling
const AGENT_TYPE_ICONS: Record<string, React.ReactNode> = {
orchestrator: <Cpu className="w-4 h-4 text-violet-600 dark:text-violet-400" />,
recon: <Scan className="w-4 h-4 text-teal-600 dark:text-teal-400" />,
analysis: <FileSearch className="w-4 h-4 text-amber-600 dark:text-amber-400" />,
verification: <ShieldCheck className="w-4 h-4 text-emerald-600 dark:text-emerald-400" />,
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" />,
};
// Agent type background colors for icon container
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_10px_rgba(52,211,153,0.4)]',
completed: '',
failed: 'shadow-[0_0_8px_rgba(251,113,133,0.3)]',
waiting: '',
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: '',
};
@ -38,105 +46,124 @@ export const AgentTreeNodeItem = memo(function AgentTreeNodeItem({
const hasChildren = node.children && node.children.length > 0;
const isSelected = selectedId === node.agent_id;
const isRunning = node.status === 'running';
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';
return (
<div className="relative">
{/* Connection line to parent - vertical line */}
{/* Connection line to parent - vertical line with gradient */}
{depth > 0 && (
<div
className="absolute top-0 w-px bg-gradient-to-b from-border to-border"
className="absolute top-0 w-px bg-gradient-to-b from-border/80 via-border/50 to-border/30"
style={{
left: `${depth * 16 - 8}px`,
height: '20px',
left: `${depth * 20 - 10}px`,
height: '22px',
}}
/>
)}
{/* Horizontal connector line */}
{/* Horizontal connector line with dot */}
{depth > 0 && (
<>
<div
className="absolute top-[20px] h-px bg-muted-foreground"
className="absolute top-[22px] h-px bg-gradient-to-r from-border/60 to-border/30"
style={{
left: `${depth * 16 - 8}px`,
width: '8px',
left: `${depth * 20 - 10}px`,
width: '10px',
}}
/>
<div
className="absolute top-[20px] w-1.5 h-1.5 rounded-full bg-border/60"
style={{
left: `${depth * 20 - 11}px`,
}}
/>
</>
)}
{/* Node item */}
{/* Node item with enhanced styling */}
<div
className={`
group relative flex items-center gap-2 py-2 px-2.5 cursor-pointer rounded-sm
transition-all duration-200 ease-out
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
${isSelected
? 'bg-primary/15 border border-primary/40'
: 'border border-transparent hover:bg-white/5 hover:border-border/50'
? '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'
}
${STATUS_GLOW_COLORS[node.status] || ''}
`}
style={{ marginLeft: `${depth * 16}px` }}
style={{ marginLeft: `${depth * 20}px` }}
onClick={() => onSelect(node.agent_id)}
>
{/* Expand/collapse button */}
{/* Expand/collapse button with enhanced styling */}
{hasChildren ? (
<button
onClick={(e) => { e.stopPropagation(); setExpanded(!expanded); }}
className="flex-shrink-0 w-5 h-5 flex items-center justify-center rounded hover:bg-white/10 transition-colors"
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'}
`}
>
{expanded ? (
<ChevronDown className="w-3.5 h-3.5 text-muted-foreground" />
<ChevronDown className="w-4 h-4 text-muted-foreground" />
) : (
<ChevronRight className="w-3.5 h-3.5 text-muted-foreground" />
<ChevronRight className="w-4 h-4 text-muted-foreground" />
)}
</button>
) : (
<span className="w-5" />
<span className="w-6" />
)}
{/* Status indicator with glow */}
{/* Status indicator with enhanced glow */}
<div className="relative flex-shrink-0">
<div className={`
w-2.5 h-2.5 rounded-full transition-all duration-300
${isRunning ? 'bg-emerald-400 animate-pulse' : ''}
${node.status === 'completed' ? 'bg-emerald-500' : ''}
${node.status === 'failed' ? 'bg-rose-400' : ''}
${node.status === 'waiting' ? 'bg-amber-400' : ''}
${node.status === 'created' ? 'bg-muted' : ''}
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' : ''}
`} />
{isRunning && (
<div className="absolute inset-0 w-2.5 h-2.5 rounded-full bg-emerald-400 animate-ping opacity-30" />
<>
<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>
{/* Agent type icon */}
<div className="flex-shrink-0">
{/* 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`}>
{typeIcon}
</div>
{/* Agent name */}
{/* Agent name with enhanced styling */}
<span className={`
text-sm font-mono truncate flex-1 transition-colors duration-200
${isSelected ? 'text-foreground font-medium' : 'text-foreground group-hover:text-foreground'}
text-sm font-mono truncate flex-1 transition-all duration-300
${isSelected ? 'text-foreground font-semibold' : 'text-foreground/90 group-hover:text-foreground'}
`}>
{node.agent_name}
</span>
{/* Metrics badges */}
<div className="flex items-center gap-1.5 flex-shrink-0">
{/* Iterations */}
{/* Metrics badges with enhanced styling */}
<div className="flex items-center gap-2 flex-shrink-0">
{/* Iterations with icon */}
{(node.iterations ?? 0) > 0 && (
<span className="text-xs text-muted-foreground font-mono bg-muted px-1.5 py-0.5 rounded">
{node.iterations}x
</span>
<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">
<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-5 px-2 text-sm bg-rose-500/25 text-rose-700 dark:text-rose-300 border border-rose-500/40 font-mono font-semibold">
{node.findings_count}
<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>
)}
</div>
@ -145,22 +172,19 @@ export const AgentTreeNodeItem = memo(function AgentTreeNodeItem({
{/* Children with animated reveal */}
{expanded && hasChildren && (
<div
className="relative"
style={{
animation: 'slideDown 0.2s ease-out',
}}
className="relative animate-in slide-in-from-top-1 duration-300"
>
{/* Vertical connection line for children */}
{/* Vertical connection line for children with gradient */}
<div
className="absolute w-px bg-gradient-to-b from-border via-border to-transparent"
className="absolute w-px bg-gradient-to-b from-border/60 via-border/40 to-transparent"
style={{
left: `${(depth + 1) * 16 - 8}px`,
left: `${(depth + 1) * 20 - 10}px`,
top: '0',
height: `calc(100% - 20px)`,
height: `calc(100% - 24px)`,
}}
/>
{node.children.map((child, index) => (
{node.children.map((child) => (
<AgentTreeNodeItem
key={child.agent_id}
node={child}
@ -171,20 +195,6 @@ export const AgentTreeNodeItem = memo(function AgentTreeNodeItem({
))}
</div>
)}
{/* Inline animation */}
<style>{`
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`}</style>
</div>
);
});

View File

@ -1,10 +1,10 @@
/**
* Header Component
* Minimalist mechanical terminal header
* Features: Subtle glow effects, refined controls
* Features: Enhanced glow effects, refined controls, premium feel
*/
import { Bot, Square, Download, Play, Loader2, Radio, Cpu } from "lucide-react";
import { Square, Download, Play, Loader2, Radio, Cpu, Sparkles } from "lucide-react";
import { Button } from "@/components/ui/button";
import { StatusBadge } from "./StatusBadge";
import type { HeaderProps } from "../types";
@ -18,74 +18,88 @@ export function Header({
onNewAudit
}: HeaderProps) {
return (
<header className="flex-shrink-0 h-14 border-b border-border flex items-center justify-between px-5 bg-card relative overflow-hidden">
{/* Subtle animated line at top */}
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-primary/30 to-transparent" />
<header className="flex-shrink-0 h-16 border-b border-border/50 flex items-center justify-between px-6 bg-card/80 backdrop-blur-md relative overflow-hidden">
{/* Animated gradient line at top */}
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-primary/50 to-transparent" />
{/* Subtle glow effect */}
<div className="absolute inset-0 bg-gradient-to-r from-primary/5 via-transparent to-primary/5 pointer-events-none" />
{/* Left side - Brand and task info */}
<div className="flex items-center gap-4">
{/* Logo section */}
<div className="flex items-center gap-2.5 pr-4 border-r border-border">
<div className="relative">
<div className="flex items-center gap-5 relative z-10">
{/* Logo section with enhanced styling */}
<div className="flex items-center gap-3 pr-5 border-r border-border/50">
<div className="relative group">
{/* Logo background glow */}
<div className="absolute inset-0 bg-primary/20 rounded-lg blur-lg opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
<div className="relative p-2 rounded-lg bg-gradient-to-br from-primary/20 to-primary/5 border border-primary/30">
<Cpu className="w-5 h-5 text-primary" />
{isRunning && (
<span className="absolute -top-0.5 -right-0.5 w-2 h-2 bg-green-400 rounded-full animate-pulse" />
<>
<span className="absolute -top-1 -right-1 w-2.5 h-2.5 bg-emerald-400 rounded-full animate-pulse shadow-[0_0_10px_rgba(52,211,153,0.6)]" />
<span className="absolute -top-1 -right-1 w-2.5 h-2.5 bg-emerald-400 rounded-full animate-ping opacity-75" />
</>
)}
</div>
<span className="font-bold text-foreground tracking-wide text-sm">
</div>
<div className="flex flex-col">
<span className="font-bold text-foreground tracking-wider text-base leading-tight">
DEEP<span className="text-primary">AUDIT</span>
</span>
<span className="text-[10px] text-muted-foreground tracking-[0.2em] uppercase">Security Agent</span>
</div>
</div>
{/* Task info */}
{/* Task info with enhanced styling */}
{task && (
<div className="flex items-center gap-3">
<div className="flex items-center gap-2 text-muted-foreground">
<Radio className="w-3 h-3" />
<span className="text-xs font-mono uppercase tracking-wider">Task</span>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 px-2.5 py-1 rounded-md bg-muted/50 border border-border/50">
<Radio className="w-3 h-3 text-muted-foreground" />
<span className="text-xs font-mono uppercase tracking-wider text-muted-foreground">Task</span>
</div>
<span className="text-foreground text-sm font-mono truncate max-w-[180px]">
<div className="flex items-center gap-3">
<span className="text-foreground text-sm font-mono truncate max-w-[200px] font-medium">
{task.name || task.id.slice(0, 8)}
</span>
<StatusBadge status={task.status} />
</div>
</div>
)}
</div>
{/* Right side - Controls */}
<div className="flex items-center gap-2">
<div className="flex items-center gap-3 relative z-10">
{isRunning && (
<Button
variant="ghost"
size="sm"
onClick={onCancel}
disabled={isCancelling}
className="h-8 px-3 text-xs font-mono uppercase tracking-wider text-red-400 hover:text-red-300 hover:bg-red-950/30 border border-transparent hover:border-red-900/50 transition-all duration-200 disabled:opacity-50"
className="h-9 px-4 text-xs font-mono uppercase tracking-wider text-rose-400 hover:text-rose-300 bg-rose-500/10 hover:bg-rose-500/20 border border-rose-500/30 hover:border-rose-500/50 transition-all duration-300 disabled:opacity-50 rounded-md shadow-[0_0_15px_rgba(244,63,94,0.1)] hover:shadow-[0_0_20px_rgba(244,63,94,0.2)]"
>
{isCancelling ? (
<>
<Loader2 className="w-3.5 h-3.5 mr-1.5 animate-spin" />
<Loader2 className="w-3.5 h-3.5 mr-2 animate-spin" />
<span>Stopping</span>
</>
) : (
<>
<Square className="w-3.5 h-3.5 mr-1.5" />
<Square className="w-3.5 h-3.5 mr-2" />
<span>Abort</span>
</>
)}
</Button>
)}
<div className="h-6 w-px bg-muted mx-1" />
<div className="h-8 w-px bg-border/50 mx-1" />
<Button
variant="ghost"
size="sm"
onClick={onExport}
disabled={!task}
className="h-8 px-3 text-xs font-mono uppercase tracking-wider text-cyan-400 hover:text-cyan-300 hover:bg-cyan-950/30 border border-transparent hover:border-cyan-900/50 transition-all duration-200 disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:border-transparent"
className="h-9 px-4 text-xs font-mono uppercase tracking-wider text-cyan-400 hover:text-cyan-300 bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/30 hover:border-cyan-500/50 transition-all duration-300 disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:border-transparent rounded-md shadow-[0_0_15px_rgba(6,182,212,0.1)] hover:shadow-[0_0_20px_rgba(6,182,212,0.2)]"
>
<Download className="w-3.5 h-3.5 mr-1.5" />
<Download className="w-3.5 h-3.5 mr-2" />
<span>Export</span>
</Button>
@ -93,16 +107,22 @@ export function Header({
variant="ghost"
size="sm"
onClick={onNewAudit}
className="h-8 px-3 text-xs font-mono uppercase tracking-wider text-primary hover:text-primary/80 hover:bg-primary/10 border border-transparent hover:border-primary/30 transition-all duration-200"
className="h-9 px-4 text-xs font-mono uppercase tracking-wider text-primary hover:text-primary/90 bg-primary/10 hover:bg-primary/20 border border-primary/30 hover:border-primary/50 transition-all duration-300 rounded-md shadow-[0_0_15px_rgba(255,107,44,0.15)] hover:shadow-[0_0_25px_rgba(255,107,44,0.25)]"
>
<Play className="w-3.5 h-3.5 mr-1.5" />
<Sparkles className="w-3.5 h-3.5 mr-2" />
<span>New Audit</span>
</Button>
</div>
{/* Subtle bottom glow when running */}
{/* Bottom accent line */}
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-border/50 to-transparent" />
{/* Enhanced bottom glow when running */}
{isRunning && (
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-1/3 h-px bg-gradient-to-r from-transparent via-green-500/50 to-transparent" />
<>
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-1/2 h-px bg-gradient-to-r from-transparent via-emerald-500/60 to-transparent" />
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-1/3 h-4 bg-gradient-to-t from-emerald-500/10 to-transparent pointer-events-none" />
</>
)}
</header>
);

View File

@ -1,30 +1,30 @@
/**
* Log Entry Component
* Terminal-style log entry with cassette futurism aesthetic
* Professional log formatting without emojis
* Terminal-style log entry with enhanced visual design
* Professional log formatting with improved readability
*/
import { memo } from "react";
import {
ChevronDown, ChevronUp, Loader2, Clock,
ChevronDown, ChevronUp, Loader2,
CheckCircle2, Wifi, XOctagon, AlertTriangle,
Play, Square, ArrowRight
Play, ArrowRight, Zap
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { LOG_TYPE_CONFIG, SEVERITY_COLORS } from "../constants";
import type { LogEntryProps } from "../types";
// Log type labels for display
// Log type labels for display with enhanced styling
const LOG_TYPE_LABELS: Record<string, string> = {
thinking: 'THINK',
tool: 'TOOL',
phase: 'PHASE',
finding: 'VULN',
dispatch: 'DISPATCH',
dispatch: 'AGENT',
info: 'INFO',
error: 'ERROR',
user: 'USER',
progress: 'PROGRESS',
progress: 'PROG',
};
// Helper to format title (remove emojis and clean up)
@ -75,6 +75,7 @@ export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: L
const isError = item.type === 'error';
const isInfo = item.type === 'info';
const isProgress = item.type === 'progress';
const isDispatch = item.type === 'dispatch';
const showContent = isThinking || isExpanded;
const isCollapsible = !isThinking && item.content;
@ -84,118 +85,121 @@ export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: L
return (
<div
className={`
group relative mb-1.5 transition-all duration-200 ease-out
group relative mb-2 transition-all duration-300 ease-out
${isCollapsible ? 'cursor-pointer' : ''}
`}
onClick={isCollapsible ? onToggle : undefined}
>
{/* Main card */}
{/* Main card with enhanced styling */}
<div className={`
relative rounded border-l-2 overflow-hidden
relative rounded-lg border-l-3 overflow-hidden backdrop-blur-sm
${config.borderColor}
${isExpanded ? 'bg-card/60' : 'bg-muted/50'}
${isCollapsible ? 'hover:bg-muted' : ''}
${isFinding ? 'border-r border-r-red-900/30' : ''}
${isError ? 'border-r border-r-red-900/30' : ''}
transition-all duration-200
${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
`}>
{/* Subtle gradient overlay */}
<div className={`absolute inset-0 opacity-20 pointer-events-none ${config.bgColor}`} />
{/* 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-3 py-2.5">
<div className="relative px-4 py-3">
{/* Header row */}
<div className="flex items-center gap-2">
{/* Type icon */}
<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' : ''}`}>
<div className={`${item.isStreaming ? 'animate-pulse' : ''} transition-transform duration-300 group-hover:scale-110`}>
{config.icon}
</div>
{item.isStreaming && (
<div className="absolute inset-0 blur-sm opacity-50">
<div className="absolute inset-0 blur-md opacity-60">
{config.icon}
</div>
)}
</div>
{/* Type label */}
{/* Type label with enhanced styling */}
<span className={`
text-sm font-mono font-bold uppercase tracking-wider px-2.5 py-1 rounded
${isThinking ? 'bg-violet-500/30 text-violet-700 dark:text-violet-300' : ''}
${isTool ? 'bg-amber-500/30 text-amber-700 dark:text-amber-300' : ''}
${isFinding ? 'bg-rose-500/30 text-rose-700 dark:text-rose-300' : ''}
${isError ? 'bg-red-500/30 text-red-700 dark:text-red-300' : ''}
${isInfo ? 'bg-muted text-foreground' : ''}
${isProgress ? 'bg-cyan-500/30 text-cyan-700 dark:text-cyan-300' : ''}
${item.type === 'dispatch' ? 'bg-sky-500/30 text-sky-700 dark:text-sky-300' : ''}
${item.type === 'phase' ? 'bg-teal-500/30 text-teal-700 dark:text-teal-300' : ''}
${item.type === 'user' ? 'bg-indigo-500/30 text-indigo-700 dark:text-indigo-300' : ''}
text-xs font-mono font-bold uppercase tracking-wider px-2 py-1 rounded-md
border transition-all duration-300
${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' : ''}
${isError ? 'bg-red-500/20 text-red-600 dark:text-red-300 border-red-500/30' : ''}
${isInfo ? 'bg-muted/80 text-foreground border-border/50' : ''}
${isProgress ? 'bg-cyan-500/20 text-cyan-600 dark:text-cyan-300 border-cyan-500/30' : ''}
${isDispatch ? 'bg-sky-500/20 text-sky-600 dark:text-sky-300 border-sky-500/30' : ''}
${item.type === 'phase' ? 'bg-teal-500/20 text-teal-600 dark:text-teal-300 border-teal-500/30' : ''}
${item.type === 'user' ? 'bg-indigo-500/20 text-indigo-600 dark:text-indigo-300 border-indigo-500/30' : ''}
flex-shrink-0
`}>
{LOG_TYPE_LABELS[item.type] || 'LOG'}
</span>
{/* Timestamp */}
<span className="text-xs text-muted-foreground font-mono flex-shrink-0">
{/* Timestamp with subtle styling */}
<span className="text-xs text-muted-foreground/80 font-mono flex-shrink-0 tabular-nums">
{item.time}
</span>
{/* Separator */}
<ArrowRight className="w-3 h-3 text-muted-foreground flex-shrink-0" />
{/* Separator with animation */}
<Zap className="w-3 h-3 text-muted-foreground/50 flex-shrink-0" />
{/* Status icon for info messages */}
{statusIcon && <span className="flex-shrink-0">{statusIcon}</span>}
{/* Title - for non-thinking types */}
{!isThinking && (
<span className="text-base text-foreground font-medium truncate flex-1">
<span className="text-sm text-foreground font-medium truncate flex-1 leading-relaxed">
{formattedTitle}
</span>
)}
{/* Streaming cursor */}
{/* Streaming cursor with enhanced animation */}
{item.isStreaming && (
<span className="w-1.5 h-4 bg-purple-400 animate-[blink_1s_ease-in-out_infinite] rounded-sm flex-shrink-0" />
<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)]" />
)}
{/* Tool status */}
{/* Tool status with enhanced styling */}
{item.tool?.status === 'running' && (
<div className="flex items-center gap-1.5 flex-shrink-0 bg-amber-500/10 px-2 py-0.5 rounded">
<Loader2 className="w-3 h-3 animate-spin text-amber-400" />
<span className="text-xs text-amber-400 font-mono uppercase">Running</span>
<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>
</div>
)}
{item.tool?.status === 'completed' && (
<div className="flex items-center gap-1 flex-shrink-0">
<CheckCircle2 className="w-3 h-3 text-green-500" />
<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>
</div>
)}
{/* Agent badge */}
{/* Agent badge with enhanced styling */}
{item.agentName && (
<Badge
variant="outline"
className="h-5 px-2 text-xs uppercase tracking-wider border-primary/40 text-primary bg-primary/10 flex-shrink-0 font-semibold"
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)]"
>
{item.agentName}
</Badge>
)}
{/* Right side info */}
<div className="flex items-center gap-2 flex-shrink-0 ml-auto">
{/* Duration badge */}
<div className="flex items-center gap-2.5 flex-shrink-0 ml-auto">
{/* Duration badge with enhanced styling */}
{item.tool?.duration !== undefined && (
<span className="text-xs text-muted-foreground font-mono bg-muted px-1.5 py-0.5 rounded">
<span className="text-xs text-muted-foreground font-mono bg-muted/80 px-2 py-1 rounded-md border border-border/50 tabular-nums">
{item.tool.duration}ms
</span>
)}
{/* Severity badge */}
{/* Severity badge with enhanced styling */}
{item.severity && (
<Badge
className={`
text-xs uppercase tracking-wider font-bold px-1.5 py-0
text-xs uppercase tracking-wider font-bold px-2 py-0.5 rounded-md
${SEVERITY_COLORS[item.severity] || SEVERITY_COLORS.info}
`}
>
@ -203,50 +207,53 @@ export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: L
</Badge>
)}
{/* Expand indicator */}
{/* Expand indicator with enhanced styling */}
{isCollapsible && (
<div className="w-5 h-5 flex items-center justify-center rounded bg-muted/50 group-hover:bg-muted/50 transition-colors">
<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'}
`}>
{isExpanded ? (
<ChevronUp className="w-3.5 h-3.5 text-muted-foreground" />
<ChevronUp className="w-4 h-4 text-primary" />
) : (
<ChevronDown className="w-3.5 h-3.5 text-muted-foreground" />
<ChevronDown className="w-4 h-4 text-muted-foreground group-hover:text-foreground transition-colors" />
)}
</div>
)}
</div>
</div>
{/* Thinking content - always visible with special styling */}
{/* Thinking content - always visible with enhanced styling */}
{isThinking && item.content && (
<div className="mt-2.5 relative">
<div className="absolute left-0 top-0 bottom-0 w-px bg-gradient-to-b from-purple-500/50 via-purple-500/20 to-transparent" />
<div className="pl-3 text-base text-foreground/90 leading-relaxed whitespace-pre-wrap break-words">
<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">
{item.content}
</div>
</div>
)}
{/* Collapsible content */}
{/* Collapsible content with enhanced styling */}
{!isThinking && showContent && item.content && (
<div className="mt-2.5 overflow-hidden">
<div className="bg-card rounded border border-border overflow-hidden">
{/* Mini header */}
<div className="flex items-center justify-between px-2.5 py-1.5 border-b border-border bg-muted">
<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="flex items-center gap-2">
<Square className="w-2.5 h-2.5 text-muted-foreground" />
<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">
{isTool ? 'Output' : 'Details'}
</span>
</div>
{item.tool?.status === 'completed' && (
<div className="flex items-center gap-1">
<CheckCircle2 className="w-3 h-3 text-green-500/70" />
<span className="text-xs text-green-500/70 font-mono">Complete</span>
<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>
</div>
)}
</div>
{/* Content */}
<pre className="p-3 text-base font-mono text-foreground/80 max-h-56 overflow-y-auto custom-scrollbar whitespace-pre-wrap break-words leading-relaxed">
{/* 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">
{item.content}
</pre>
</div>

View File

@ -1,22 +1,23 @@
/**
* Splash Screen Component
* Cassette Futurism / Terminal Retro aesthetic
* Features: Boot sequence, command input, CSS styled logo
* Premium terminal aesthetic with enhanced animations
* Features: Boot sequence, command input, animated logo, particle effects
*/
import { useState, useEffect, useRef, useCallback } from "react";
import { Shield, Zap } from "lucide-react";
interface SplashScreenProps {
onComplete: () => void;
}
// Boot sequence messages
// Enhanced boot sequence messages with icons
const BOOT_SEQUENCE = [
{ text: "[INIT] Loading DeepAudit Core...", delay: 0 },
{ text: "[SCAN] Neural Analysis Engine v3.0", delay: 150 },
{ text: "[LOAD] Vulnerability Pattern Database", delay: 300 },
{ text: "[SYNC] Agent Orchestration Module", delay: 450 },
{ text: "[READY] System Online", delay: 600 },
{ text: "[INIT] Loading DeepAudit Core...", delay: 0, type: 'init' },
{ text: "[SCAN] Neural Analysis Engine v3.0", delay: 200, type: 'scan' },
{ text: "[LOAD] Vulnerability Pattern Database", delay: 400, type: 'load' },
{ text: "[SYNC] Agent Orchestration Module", delay: 600, type: 'sync' },
{ text: "[READY] System Online", delay: 800, type: 'ready' },
];
// Available commands
@ -151,102 +152,195 @@ export function SplashScreen({ onComplete }: SplashScreenProps) {
};
return (
<div className="h-screen cyber-bg-elevated flex flex-col overflow-hidden relative">
{/* Scanline overlay */}
<div className="absolute inset-0 pointer-events-none z-20">
<div
className="absolute inset-0"
style={{
backgroundImage: "repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,0.1) 2px, rgba(0,0,0,0.1) 4px)",
}}
/>
<div className="h-screen bg-gray-100 dark:bg-black flex flex-col overflow-hidden relative cyber-splash">
{/* Gradient background - light/dark adaptive */}
<div className="absolute inset-0 bg-gradient-to-br from-gray-50 via-gray-100 to-gray-200 dark:from-black dark:via-gray-950 dark:to-black pointer-events-none" />
{/* Animated cyber grid - adaptive opacity */}
<div className="absolute inset-0 cyber-grid opacity-20 dark:opacity-30 pointer-events-none" />
{/* Horizontal scan line animation - dark mode only */}
<div className="absolute inset-0 pointer-events-none z-20 overflow-hidden hidden dark:block">
<div className="scan-line" />
</div>
{/* Vignette effect */}
{/* CRT screen effect - dark mode only */}
<div className="absolute inset-0 pointer-events-none z-10 crt-effect hidden dark:block" />
{/* Glitch overlay - dark mode only */}
<div className="absolute inset-0 pointer-events-none z-15 glitch-overlay hidden dark:block" />
{/* Vignette effect - light mode only */}
<div
className="absolute inset-0 pointer-events-none z-10"
className="absolute inset-0 pointer-events-none z-10 dark:hidden"
style={{
background: "radial-gradient(ellipse at center, transparent 0%, transparent 50%, rgba(0,0,0,0.5) 100%)",
background: "radial-gradient(ellipse at center, transparent 0%, transparent 50%, rgba(0,0,0,0.15) 100%)",
}}
/>
{/* Vignette effect - dark mode only */}
<div
className="absolute inset-0 pointer-events-none z-10 hidden dark:block"
style={{
background: "radial-gradient(ellipse at center, transparent 0%, transparent 40%, rgba(0,0,0,0.7) 100%)",
}}
/>
{/* Grid background */}
{/* Animated data streams - dark mode only */}
<div className="hidden dark:block">
<div className="absolute left-4 top-0 bottom-0 w-px overflow-hidden pointer-events-none z-20">
<div className="data-stream" />
</div>
<div className="absolute left-8 top-0 bottom-0 w-px overflow-hidden pointer-events-none z-20">
<div className="data-stream" style={{ animationDelay: '0.5s' }} />
</div>
<div className="absolute right-4 top-0 bottom-0 w-px overflow-hidden pointer-events-none z-20">
<div className="data-stream" style={{ animationDelay: '1s' }} />
</div>
<div className="absolute right-8 top-0 bottom-0 w-px overflow-hidden pointer-events-none z-20">
<div className="data-stream" style={{ animationDelay: '1.5s' }} />
</div>
</div>
{/* Floating hex codes - dark mode only */}
<div className="absolute inset-0 pointer-events-none z-5 overflow-hidden hidden dark:block">
{[...Array(8)].map((_, i) => (
<div
className="absolute inset-0 opacity-[0.03]"
key={i}
className="hex-float text-primary/20 text-xs font-mono"
style={{
backgroundImage: `
linear-gradient(rgba(255,107,44,0.5) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,107,44,0.5) 1px, transparent 1px)
`,
backgroundSize: "32px 32px",
left: `${10 + i * 12}%`,
animationDelay: `${i * 0.7}s`,
}}
/>
>
{Math.random().toString(16).substr(2, 8).toUpperCase()}
</div>
))}
</div>
{/* Geometric accent lines - adaptive */}
<div className="absolute top-0 left-0 w-32 dark:w-48 h-px bg-gradient-to-r from-primary/60 dark:from-primary via-primary/30 dark:via-primary/50 to-transparent pointer-events-none z-30" />
<div className="absolute top-0 left-0 w-px h-32 dark:h-48 bg-gradient-to-b from-primary/60 dark:from-primary via-primary/30 dark:via-primary/50 to-transparent pointer-events-none z-30" />
<div className="absolute bottom-0 right-0 w-32 dark:w-48 h-px bg-gradient-to-l from-primary/60 dark:from-primary via-primary/30 dark:via-primary/50 to-transparent pointer-events-none z-30" />
<div className="absolute bottom-0 right-0 w-px h-32 dark:h-48 bg-gradient-to-t from-primary/60 dark:from-primary via-primary/30 dark:via-primary/50 to-transparent pointer-events-none z-30" />
{/* Main content */}
<div className="flex-1 flex items-center justify-center p-4 relative z-30">
<div className="w-full max-w-2xl">
{/* CSS Logo */}
<div className={`text-center mb-8 transition-all duration-700 ${showLogo ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-4"}`}>
{/* Logo with adaptive styling */}
<div className={`text-center mb-10 transition-all duration-1000 ${showLogo ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-8"}`}>
{/* Logo text - light mode: clean, dark mode: neon glow */}
<div className="logo-glitch relative inline-block">
<div
className="text-4xl sm:text-5xl md:text-6xl font-bold tracking-wider mb-2 font-mono"
style={{ textShadow: "0 0 30px rgba(255,107,44,0.5), 0 0 60px rgba(255,107,44,0.3)" }}
className="text-5xl sm:text-6xl md:text-7xl font-bold tracking-wider mb-3 font-mono relative logo-text"
>
<span className="text-primary">DEEP</span>
<span className="text-foreground">AUDIT</span>
<span className="text-gray-800 dark:text-white">AUDIT</span>
</div>
<div className="text-muted-foreground text-xs sm:text-sm tracking-[0.3em] uppercase">
Autonomous Security Agent
{/* Glitch layers - dark mode only, opacity controlled by CSS */}
<div className="glitch-layer glitch-layer-1 text-5xl sm:text-6xl md:text-7xl font-bold tracking-wider font-mono absolute top-0 left-0 w-full opacity-0 dark:opacity-0">
<span className="text-cyan-500">DEEP</span>
<span className="text-white">AUDIT</span>
</div>
<div className="glitch-layer glitch-layer-2 text-5xl sm:text-6xl md:text-7xl font-bold tracking-wider font-mono absolute top-0 left-0 w-full opacity-0 dark:opacity-0">
<span className="text-red-500">DEEP</span>
<span className="text-white">AUDIT</span>
</div>
</div>
{/* Subtitle - adaptive styling */}
<div className="flex items-center justify-center gap-3 text-gray-500 dark:text-gray-400 text-sm tracking-[0.3em] uppercase mt-4">
<div className="w-12 h-px bg-gradient-to-r from-transparent via-primary/40 dark:via-cyan-500/50 to-transparent" />
<Shield className="w-4 h-4 text-primary/70 dark:text-cyan-500/70" />
<span className="dark:cyber-text">Autonomous Security Agent</span>
<Shield className="w-4 h-4 text-primary/70 dark:text-cyan-500/70" />
<div className="w-12 h-px bg-gradient-to-r from-transparent via-primary/40 dark:via-cyan-500/50 to-transparent" />
</div>
{/* Version tag */}
<div className="mt-2 text-[10px] font-mono text-primary/50 tracking-widest">
[ v3.0.0 // NEURAL_CORE ]
</div>
</div>
{/* Terminal window */}
{/* Terminal window - adaptive styling */}
<div
className="cyber-dialog border border-border rounded-lg overflow-hidden shadow-2xl"
className="relative rounded-xl overflow-hidden bg-white dark:bg-transparent border border-gray-200 dark:border-transparent shadow-xl dark:shadow-none"
onClick={handleTerminalClick}
>
{/* Terminal header */}
<div className="flex items-center gap-2 px-4 py-2.5 cyber-bg-elevated border-b border-border">
<div className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded-full bg-red-500/80" />
<div className="w-3 h-3 rounded-full bg-yellow-500/80" />
<div className="w-3 h-3 rounded-full bg-green-500/80" />
{/* Terminal border glow - dark mode only */}
<div className="absolute inset-0 rounded-xl border border-primary/30 pointer-events-none hidden dark:block" />
<div className="absolute inset-0 rounded-xl shadow-[0_0_30px_rgba(255,107,44,0.2),inset_0_0_30px_rgba(0,0,0,0.5)] pointer-events-none hidden dark:block" />
{/* Terminal header - adaptive */}
<div className="relative flex items-center gap-3 px-4 py-2.5 bg-gray-100 dark:bg-gray-950 border-b border-gray-200 dark:border-primary/20">
{/* Window dots */}
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-400 dark:bg-red-500 dark:shadow-[0_0_10px_rgba(239,68,68,0.8)]" />
<div className="w-3 h-3 rounded-full bg-yellow-400 dark:bg-yellow-500 dark:shadow-[0_0_10px_rgba(234,179,8,0.8)]" />
<div className="w-3 h-3 rounded-full bg-green-400 dark:bg-green-500 dark:shadow-[0_0_10px_rgba(34,197,94,0.8)]" />
</div>
<span className="text-xs text-muted-foreground ml-3 font-mono tracking-wider">
deepaudit@terminal
{/* Terminal title */}
<div className="flex-1 flex items-center justify-center gap-2">
<span className="text-primary/60 text-xs"></span>
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono tracking-[0.15em] uppercase">
root@deepaudit:~#
</span>
<span className="w-2 h-4 bg-primary/80 animate-pulse" />
</div>
{/* Status indicator */}
<div className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse dark:shadow-[0_0_6px_rgba(52,211,153,0.8)]" />
<span className="text-[10px] text-emerald-600 dark:text-emerald-500/80 font-mono">LIVE</span>
</div>
</div>
{/* Terminal content */}
{/* Terminal content - adaptive */}
<div
ref={terminalRef}
className="p-4 font-mono text-sm h-72 overflow-y-auto custom-scrollbar"
className="relative p-5 font-mono text-sm h-80 overflow-y-auto custom-scrollbar bg-gray-50 dark:bg-gray-950/95"
>
{/* Boot logs */}
{/* Boot logs - adaptive styling */}
{bootLogs.map((log, i) => (
<div
key={`boot-${i}`}
className={`mb-1 ${
log.includes("[READY]") ? "text-green-400" :
className={`mb-2 flex items-center gap-2 ${
log.includes("[READY]") ? "text-emerald-600 dark:text-emerald-400" :
log.includes("[INIT]") ? "text-primary" :
"text-muted-foreground"
log.includes("[SCAN]") ? "text-violet-600 dark:text-violet-400" :
log.includes("[LOAD]") ? "text-amber-600 dark:text-amber-400" :
log.includes("[SYNC]") ? "text-cyan-600 dark:text-cyan-400" :
"text-gray-500"
}`}
style={{
animation: "fadeSlideIn 0.2s ease-out",
animation: "fadeSlideIn 0.3s ease-out",
animationFillMode: "both",
animationDelay: `${i * 0.05}s`
animationDelay: `${i * 0.08}s`
}}
>
<span className="text-muted-foreground mr-2">&gt;</span>
{log}
<span className="text-emerald-600 dark:text-emerald-500/60">$</span>
<span className={`w-1.5 h-1.5 rounded-full ${
log.includes("[READY]") ? "bg-emerald-500 dark:shadow-[0_0_8px_rgba(52,211,153,0.8)]" :
log.includes("[INIT]") ? "bg-primary dark:shadow-[0_0_8px_rgba(255,107,44,0.8)]" :
log.includes("[SCAN]") ? "bg-violet-500 dark:shadow-[0_0_8px_rgba(167,139,250,0.8)]" :
log.includes("[LOAD]") ? "bg-amber-500 dark:shadow-[0_0_8px_rgba(251,191,36,0.8)]" :
log.includes("[SYNC]") ? "bg-cyan-500 dark:shadow-[0_0_8px_rgba(34,211,238,0.8)]" :
"bg-gray-400 dark:bg-gray-600"
}`} />
<span>{log}</span>
</div>
))}
{/* Welcome message */}
{/* Welcome message - adaptive */}
{bootComplete && (
<div className="mt-4 mb-4 pt-3 border-t border-border">
<div className="text-primary mb-1">Welcome to DeepAudit Agent Terminal</div>
<div className="text-muted-foreground text-xs">
Type <span className="text-emerald-400 font-semibold">'audit'</span> to start a new security audit, or <span className="text-muted-foreground">'help'</span> for commands.
<div className="mt-5 mb-4 pt-4 border-t border-gray-200 dark:border-primary/20">
<div className="flex items-center gap-2 text-primary mb-2">
<Zap className="w-4 h-4 text-cyan-600 dark:text-cyan-400" />
<span className="font-semibold text-cyan-600 dark:text-cyan-400">// SYSTEM READY</span>
</div>
<div className="text-gray-600 dark:text-gray-400 text-sm pl-6">
Execute <span className="text-emerald-600 dark:text-emerald-400 font-bold px-2 py-0.5 bg-emerald-500/10 dark:bg-emerald-500/20 border border-emerald-500/30 rounded">'audit'</span> to initialize security scan protocol
</div>
<div className="text-gray-400 dark:text-gray-600 text-xs pl-6 mt-1">
[ Type 'help' for available commands ]
</div>
</div>
)}
@ -308,33 +402,174 @@ export function SplashScreen({ onComplete }: SplashScreenProps) {
</div>
</div>
{/* Corner decorations */}
<div className="absolute top-4 left-4 text-xs font-mono text-muted-foreground z-30">
<div>SYS.VERSION: 3.0.0</div>
<div>MODE: INTERACTIVE</div>
</div>
<div className="absolute top-4 right-4 text-xs font-mono text-muted-foreground text-right z-30">
<div>MEM: 16384MB</div>
<div>STATUS: READY</div>
</div>
<div className="absolute bottom-4 left-4 text-xs font-mono text-muted-foreground z-30">
DEEPAUDIT_AGENT_v3
</div>
<div className="absolute bottom-4 right-4 text-xs font-mono text-muted-foreground z-30">
{new Date().toISOString().split("T")[0]}
</div>
{/* Animation styles */}
{/* Cyberpunk Animation Styles */}
<style>{`
/* Fade slide in animation */
@keyframes fadeSlideIn {
from {
from { opacity: 0; transform: translateX(-12px); }
to { opacity: 1; transform: translateX(0); }
}
/* Cyber grid background - adaptive */
.cyber-grid {
background-image:
linear-gradient(rgba(255,107,44,0.15) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,107,44,0.15) 1px, transparent 1px);
background-size: 50px 50px;
animation: gridMove 20s linear infinite;
}
.dark .cyber-grid {
background-image:
linear-gradient(rgba(255,107,44,0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,107,44,0.1) 1px, transparent 1px);
}
@keyframes gridMove {
0% { background-position: 0 0; }
100% { background-position: 50px 50px; }
}
/* Horizontal scan line */
.scan-line {
position: absolute;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(255,107,44,0.5), rgba(0,255,255,0.3), transparent);
box-shadow: 0 0 10px rgba(255,107,44,0.5), 0 0 20px rgba(0,255,255,0.3);
animation: scanLine 4s linear infinite;
}
@keyframes scanLine {
0% { top: -2px; opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { top: 100%; opacity: 0; }
}
/* CRT screen effect */
.crt-effect {
background: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.15),
rgba(0, 0, 0, 0.15) 1px,
transparent 1px,
transparent 2px
);
}
/* Glitch overlay */
.glitch-overlay {
animation: glitchOverlay 8s infinite;
}
@keyframes glitchOverlay {
0%, 95%, 100% { opacity: 0; }
96% { opacity: 0.1; background: rgba(255,0,0,0.1); transform: translateX(-2px); }
97% { opacity: 0; }
98% { opacity: 0.1; background: rgba(0,255,255,0.1); transform: translateX(2px); }
99% { opacity: 0; }
}
/* Data stream animation */
.data-stream {
width: 100%;
height: 200%;
background: linear-gradient(
180deg,
transparent 0%,
rgba(0,255,255,0.3) 10%,
rgba(255,107,44,0.5) 50%,
rgba(0,255,255,0.3) 90%,
transparent 100%
);
animation: dataStream 3s linear infinite;
}
@keyframes dataStream {
0% { transform: translateY(-50%); }
100% { transform: translateY(0%); }
}
/* Floating hex codes */
.hex-float {
position: absolute;
animation: hexFloat 15s linear infinite;
}
@keyframes hexFloat {
0% { top: 100%; opacity: 0; }
10% { opacity: 0.3; }
90% { opacity: 0.3; }
100% { top: -10%; opacity: 0; }
}
/* Glow line animation */
.glow-line {
animation: glowPulse 2s ease-in-out infinite;
}
@keyframes glowPulse {
0%, 100% { opacity: 0.6; filter: drop-shadow(0 0 2px currentColor); }
50% { opacity: 1; filter: drop-shadow(0 0 8px currentColor); }
}
/* Logo glitch effect - dark mode only on hover */
.logo-glitch .glitch-layer {
opacity: 0;
transform: translateX(-8px);
pointer-events: none;
}
to {
opacity: 1;
transform: translateX(0);
.dark .logo-glitch:hover .glitch-layer-1 {
animation: glitch1 0.3s infinite;
}
.dark .logo-glitch:hover .glitch-layer-2 {
animation: glitch2 0.3s infinite;
}
@keyframes glitch1 {
0%, 100% { opacity: 0; transform: translate(0); }
20% { opacity: 0.8; transform: translate(-2px, 2px); }
40% { opacity: 0; transform: translate(0); }
60% { opacity: 0.8; transform: translate(2px, -1px); }
80% { opacity: 0; transform: translate(0); }
}
@keyframes glitch2 {
0%, 100% { opacity: 0; transform: translate(0); }
25% { opacity: 0.6; transform: translate(2px, -2px); }
50% { opacity: 0; transform: translate(0); }
75% { opacity: 0.6; transform: translate(-2px, 1px); }
}
/* Neon text effect - dark mode only */
.dark .logo-text {
text-shadow:
0 0 10px rgba(255,107,44,0.8),
0 0 20px rgba(255,107,44,0.6),
0 0 40px rgba(255,107,44,0.4),
0 0 80px rgba(255,107,44,0.2);
}
/* Light mode logo - subtle shadow */
.logo-text {
text-shadow: 0 2px 10px rgba(255,107,44,0.2);
}
/* Cyber text styling - dark mode only */
.dark .cyber-text {
text-shadow: 0 0 10px rgba(0,255,255,0.5);
}
/* Terminal cyber styling - dark mode */
.dark .terminal-cyber {
background: linear-gradient(180deg, rgba(10,10,10,0.98) 0%, rgba(5,5,5,0.99) 100%);
}
/* Text glow - dark mode only */
.dark .text-glow-success {
text-shadow: 0 0 10px rgba(52,211,153,0.8), 0 0 20px rgba(52,211,153,0.4);
}
`}</style>
</div>

View File

@ -1,17 +1,17 @@
/**
* Stats Panel Component
* Dashboard-style statistics with cassette futurism aesthetic
* Dashboard-style statistics with premium visual design
* Features: Animated progress, metric gauges, severity indicators
* Enhanced color palette for better visibility
* Enhanced visual effects with depth and polish
*/
import { memo } from "react";
import { Activity, FileCode, Repeat, Zap, Bug, Shield, AlertTriangle } from "lucide-react";
import { Activity, FileCode, Repeat, Zap, Bug, Shield, AlertTriangle, TrendingUp, Clock } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import type { StatsPanelProps } from "../types";
// Circular progress component
function CircularProgress({ value, size = 48, strokeWidth = 3, color = "primary" }: {
// Enhanced Circular progress component with glow effect
function CircularProgress({ value, size = 52, strokeWidth = 4, color = "primary" }: {
value: number;
size?: number;
strokeWidth?: number;
@ -21,61 +21,86 @@ function CircularProgress({ value, size = 48, strokeWidth = 3, color = "primary"
const circumference = radius * 2 * Math.PI;
const offset = circumference - (value / 100) * circumference;
const colorMap: Record<string, string> = {
primary: '#FF6B2C',
emerald: '#34d399',
rose: '#fb7185',
amber: '#fbbf24',
const colorMap: Record<string, { stroke: string; glow: string }> = {
primary: { stroke: '#FF6B2C', glow: 'rgba(255,107,44,0.4)' },
emerald: { stroke: '#34d399', glow: 'rgba(52,211,153,0.4)' },
rose: { stroke: '#fb7185', glow: 'rgba(251,113,133,0.4)' },
amber: { stroke: '#fbbf24', glow: 'rgba(251,191,36,0.4)' },
};
const colors = colorMap[color] || colorMap.primary;
return (
<svg width={size} height={size} className="transform -rotate-90">
{/* Background circle */}
{/* Background circle with subtle gradient */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke="rgba(255,255,255,0.1)"
stroke="rgba(255,255,255,0.08)"
strokeWidth={strokeWidth}
/>
{/* Glow effect circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke={colors.stroke}
strokeWidth={strokeWidth + 4}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap="round"
className="transition-all duration-700 ease-out opacity-20 blur-sm"
/>
{/* Progress circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke={colorMap[color] || colorMap.primary}
stroke={colors.stroke}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap="round"
className="transition-all duration-700 ease-out"
style={{
filter: `drop-shadow(0 0 6px ${colorMap[color] || colorMap.primary}50)`,
filter: `drop-shadow(0 0 8px ${colors.glow})`,
}}
/>
</svg>
);
}
// Metric card component with enhanced colors
function MetricCard({ icon, label, value, suffix = "", colorClass = "text-muted-foreground" }: {
// Enhanced Metric card component with premium styling
function MetricCard({ icon, label, value, suffix = "", colorClass = "text-muted-foreground", bgClass = "" }: {
icon: React.ReactNode;
label: string;
value: string | number;
suffix?: string;
colorClass?: string;
bgClass?: string;
}) {
return (
<div className="flex items-center gap-2.5 p-3 rounded bg-card border border-border">
<div className={colorClass}>
<div className={`
group relative flex items-center gap-3 p-3.5 rounded-lg
bg-card/80 border border-border/50 backdrop-blur-sm
hover:bg-card hover:border-border/80 hover:shadow-md
transition-all duration-300
${bgClass}
`}>
{/* Subtle gradient overlay on hover */}
<div className="absolute inset-0 rounded-lg bg-gradient-to-br from-white/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none" />
<div className={`relative z-10 p-2 rounded-md bg-muted/50 border border-border/50 ${colorClass} transition-transform duration-300 group-hover:scale-105`}>
{icon}
</div>
<div className="flex-1 min-w-0">
<div className="text-sm text-muted-foreground uppercase tracking-wider truncate font-medium">{label}</div>
<div className="text-lg text-foreground font-mono font-bold">
{value}<span className="text-muted-foreground text-base">{suffix}</span>
<div className="flex-1 min-w-0 relative z-10">
<div className="text-xs text-muted-foreground uppercase tracking-wider truncate font-medium mb-0.5">{label}</div>
<div className="text-lg text-foreground font-mono font-bold leading-tight">
{value}<span className="text-muted-foreground text-sm ml-0.5">{suffix}</span>
</div>
</div>
</div>
@ -105,131 +130,188 @@ export const StatsPanel = memo(function StatsPanel({ task, findings }: StatsPane
return (
<div className="space-y-3">
{/* Progress Section */}
<div className="p-4 rounded border border-border bg-card">
{/* Progress Section with enhanced styling */}
<div className="p-4 rounded-lg border border-border/50 bg-card/80 backdrop-blur-sm relative overflow-hidden">
{/* Background gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent pointer-events-none" />
<div className="relative z-10">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Activity className="w-3.5 h-3.5 text-primary" />
<span className="text-sm text-muted-foreground uppercase tracking-wider font-medium">Progress</span>
<div className="flex items-center gap-2.5">
<div className="p-1.5 rounded-md bg-primary/15 border border-primary/30">
<Activity className="w-4 h-4 text-primary" />
</div>
<span className="text-sm text-foreground uppercase tracking-wider font-semibold">Progress</span>
</div>
<div className="flex items-center gap-2">
<span className="text-lg text-primary font-mono font-bold">{progressPercent.toFixed(0)}</span>
<span className="text-sm text-muted-foreground">%</span>
</div>
<span className="text-xs text-primary font-mono font-bold">{progressPercent.toFixed(0)}%</span>
</div>
{/* Progress bar */}
<div className="relative h-2 bg-muted rounded-full overflow-hidden">
{/* Enhanced Progress bar */}
<div className="relative h-3 bg-muted/50 rounded-full overflow-hidden border border-border/30">
<div
className="absolute inset-y-0 left-0 bg-gradient-to-r from-primary to-primary/80 rounded-full transition-all duration-700 ease-out"
className="absolute inset-y-0 left-0 bg-gradient-to-r from-primary via-primary to-primary/80 rounded-full transition-all duration-700 ease-out"
style={{ width: `${progressPercent}%` }}
/>
{/* Shine effect */}
{/* Animated shine effect */}
<div
className="absolute inset-y-0 left-0 bg-gradient-to-r from-transparent via-white/20 to-transparent rounded-full"
className="absolute inset-y-0 left-0 bg-gradient-to-r from-transparent via-white/30 to-transparent rounded-full"
style={{
width: `${progressPercent}%`,
animation: 'shine 2s ease-in-out infinite',
}}
/>
{/* Glow effect */}
<div
className="absolute inset-y-0 left-0 rounded-full blur-sm opacity-50"
style={{
width: `${progressPercent}%`,
background: 'linear-gradient(to right, #FF6B2C, #FF6B2C)',
}}
/>
</div>
{/* File progress */}
<div className="flex items-center justify-between mt-3 text-base">
<span className="text-muted-foreground font-medium">Files scanned</span>
{/* File progress with enhanced styling */}
<div className="flex items-center justify-between mt-4 text-sm">
<div className="flex items-center gap-2 text-muted-foreground">
<FileCode className="w-4 h-4" />
<span className="font-medium">Files scanned</span>
</div>
<span className="text-foreground font-mono font-bold">
{task.analyzed_files}<span className="text-muted-foreground font-normal">/{task.total_files}</span>
{task.analyzed_files}<span className="text-muted-foreground font-normal"> / {task.total_files}</span>
</span>
</div>
{/* Files with findings */}
{task.files_with_findings > 0 && (
<div className="flex items-center justify-between mt-2 text-base">
<span className="text-muted-foreground font-medium">Files with findings</span>
<span className="text-rose-500 dark:text-rose-400 font-mono font-bold">
<div className="flex items-center justify-between mt-2 text-sm">
<div className="flex items-center gap-2 text-muted-foreground">
<AlertTriangle className="w-4 h-4 text-rose-500" />
<span className="font-medium">Files with findings</span>
</div>
<span className="text-rose-500 font-mono font-bold">
{task.files_with_findings}
</span>
</div>
)}
</div>
</div>
{/* Metrics Grid */}
<div className="grid grid-cols-2 gap-2">
{/* Metrics Grid with enhanced styling */}
<div className="grid grid-cols-2 gap-2.5">
<MetricCard
icon={<Repeat className="w-4 h-4" />}
label="Iterations"
value={task.total_iterations || 0}
colorClass="text-teal-600 dark:text-teal-400"
colorClass="text-teal-500"
/>
<MetricCard
icon={<Zap className="w-4 h-4" />}
label="Tool Calls"
value={task.tool_calls_count || 0}
colorClass="text-amber-600 dark:text-amber-400"
colorClass="text-amber-500"
/>
<MetricCard
icon={<FileCode className="w-4 h-4" />}
icon={<TrendingUp className="w-4 h-4" />}
label="Tokens"
value={((task.tokens_used || 0) / 1000).toFixed(1)}
suffix="k"
colorClass="text-violet-600 dark:text-violet-400"
colorClass="text-violet-500"
/>
<MetricCard
icon={<Bug className="w-4 h-4" />}
label="Findings"
value={totalFindings}
colorClass={totalFindings > 0 ? "text-rose-600 dark:text-rose-400" : "text-muted-foreground"}
colorClass={totalFindings > 0 ? "text-rose-500" : "text-muted-foreground"}
bgClass={totalFindings > 0 ? "border-rose-500/20" : ""}
/>
</div>
{/* Findings breakdown */}
{/* Findings breakdown with enhanced styling */}
{totalFindings > 0 && (
<div className="p-4 rounded border border-border bg-card">
<div className="flex items-center gap-2 mb-2">
<AlertTriangle className="w-3.5 h-3.5 text-rose-400" />
<span className="text-sm text-muted-foreground uppercase tracking-wider font-medium">Severity Breakdown</span>
<div className="p-4 rounded-lg border border-rose-500/20 bg-card/80 backdrop-blur-sm relative overflow-hidden">
{/* Background gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-rose-500/5 to-transparent pointer-events-none" />
<div className="relative z-10">
<div className="flex items-center gap-2.5 mb-3">
<div className="p-1.5 rounded-md bg-rose-500/15 border border-rose-500/30">
<AlertTriangle className="w-4 h-4 text-rose-500" />
</div>
<span className="text-sm text-foreground uppercase tracking-wider font-semibold">Severity Breakdown</span>
</div>
<div className="flex flex-wrap gap-1.5">
<div className="flex flex-wrap gap-2">
{severityCounts.critical > 0 && (
<Badge className="bg-rose-500/25 text-rose-700 dark:text-rose-300 border border-rose-500/40 text-sm font-mono font-semibold">
CRIT: {severityCounts.critical}
<Badge className="bg-rose-500/20 text-rose-600 dark:text-rose-300 border border-rose-500/40 text-xs font-mono font-bold px-2.5 py-1 shadow-[0_0_10px_rgba(244,63,94,0.15)]">
CRITICAL: {severityCounts.critical}
</Badge>
)}
{severityCounts.high > 0 && (
<Badge className="bg-orange-500/25 text-orange-700 dark:text-orange-300 border border-orange-500/40 text-sm font-mono font-semibold">
<Badge className="bg-orange-500/20 text-orange-600 dark:text-orange-300 border border-orange-500/40 text-xs font-mono font-bold px-2.5 py-1 shadow-[0_0_10px_rgba(249,115,22,0.15)]">
HIGH: {severityCounts.high}
</Badge>
)}
{severityCounts.medium > 0 && (
<Badge className="bg-amber-500/25 text-amber-700 dark:text-amber-300 border border-amber-500/40 text-sm font-mono font-semibold">
MED: {severityCounts.medium}
<Badge className="bg-amber-500/20 text-amber-600 dark:text-amber-300 border border-amber-500/40 text-xs font-mono font-bold px-2.5 py-1 shadow-[0_0_10px_rgba(245,158,11,0.15)]">
MEDIUM: {severityCounts.medium}
</Badge>
)}
{severityCounts.low > 0 && (
<Badge className="bg-sky-500/25 text-sky-700 dark:text-sky-300 border border-sky-500/40 text-sm font-mono font-semibold">
<Badge className="bg-sky-500/20 text-sky-600 dark:text-sky-300 border border-sky-500/40 text-xs font-mono font-bold px-2.5 py-1 shadow-[0_0_10px_rgba(14,165,233,0.15)]">
LOW: {severityCounts.low}
</Badge>
)}
</div>
</div>
</div>
)}
{/* Security Score */}
{/* Security Score with enhanced styling */}
{task.security_score !== null && task.security_score !== undefined && (
<div className="p-4 rounded border border-border bg-card">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Shield className="w-3.5 h-3.5 text-emerald-400" />
<span className="text-sm text-muted-foreground uppercase tracking-wider font-medium">Security Score</span>
<div className="p-4 rounded-lg border border-emerald-500/20 bg-card/80 backdrop-blur-sm relative overflow-hidden">
{/* Background gradient based on score */}
<div className={`absolute inset-0 bg-gradient-to-br pointer-events-none ${
task.security_score >= 80 ? 'from-emerald-500/5' :
task.security_score >= 60 ? 'from-amber-500/5' :
'from-rose-500/5'
} to-transparent`} />
<div className="relative z-10 flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className={`p-1.5 rounded-md border ${
task.security_score >= 80 ? 'bg-emerald-500/15 border-emerald-500/30' :
task.security_score >= 60 ? 'bg-amber-500/15 border-amber-500/30' :
'bg-rose-500/15 border-rose-500/30'
}`}>
<Shield className={`w-4 h-4 ${
task.security_score >= 80 ? 'text-emerald-500' :
task.security_score >= 60 ? 'text-amber-500' :
'text-rose-500'
}`} />
</div>
<div>
<span className="text-sm text-foreground uppercase tracking-wider font-semibold block">Security Score</span>
<span className="text-xs text-muted-foreground">
{task.security_score >= 80 ? 'Excellent' :
task.security_score >= 60 ? 'Good' :
'Needs Attention'}
</span>
</div>
</div>
<div className="relative">
<CircularProgress
value={task.security_score}
size={44}
strokeWidth={3}
size={56}
strokeWidth={4}
color={getScoreColor(task.security_score)}
/>
<div className="absolute inset-0 flex items-center justify-center">
<span className={`text-sm font-bold font-mono ${task.security_score >= 80 ? 'text-emerald-600 dark:text-emerald-400' :
task.security_score >= 60 ? 'text-amber-600 dark:text-amber-400' :
'text-rose-600 dark:text-rose-400'
<span className={`text-base font-bold font-mono ${
task.security_score >= 80 ? 'text-emerald-500' :
task.security_score >= 60 ? 'text-amber-500' :
'text-rose-500'
}`}>
{task.security_score.toFixed(0)}
</span>

View File

@ -772,10 +772,17 @@ function AgentAuditPageContent() {
return (
<div className="h-screen bg-background flex flex-col overflow-hidden relative">
{/* Subtle grid background */}
<div className="absolute inset-0 cyber-grid-subtle opacity-40 pointer-events-none" />
{/* 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-50" />
<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
@ -790,58 +797,72 @@ 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">
<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" />
{/* Log header */}
<div className="flex-shrink-0 h-11 border-b border-border flex items-center justify-between px-4 cyber-bg-elevated/90 backdrop-blur-sm">
<div className="flex items-center gap-3 text-xs text-muted-foreground">
<div className="flex items-center gap-2">
<Terminal className="w-4 h-4 text-muted-foreground" />
<span className="uppercase font-bold tracking-[0.15em] text-foreground">Activity Log</span>
<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 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>
</div>
{isConnected && (
<div className="flex items-center gap-1.5 text-emerald-400">
<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_8px_rgba(61,214,140,0.5)]"></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">Live</span>
<span className="text-xs font-mono uppercase tracking-wider text-emerald-400 font-semibold">Live</span>
</div>
)}
<Badge variant="outline" className="h-5 px-1.5 text-xs border-border text-muted-foreground font-mono bg-muted">
{filteredLogs.length}{!showAllLogs && logs.length !== filteredLogs.length ? ` / ${logs.length}` : ''}
<Badge variant="outline" className="h-6 px-2 text-xs border-border/60 text-muted-foreground font-mono bg-muted/50 backdrop-blur-sm">
{filteredLogs.length}{!showAllLogs && logs.length !== filteredLogs.length ? ` / ${logs.length}` : ''} entries
</Badge>
</div>
<button
onClick={() => setAutoScroll(!isAutoScroll)}
className={`
flex items-center gap-1.5 text-xs px-2.5 py-1 rounded font-mono uppercase tracking-wider
transition-all duration-200
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
${isAutoScroll
? 'bg-primary/15 text-primary border border-primary/40 shadow-[0_0_10px_rgba(255,95,31,0.15)]'
: 'text-muted-foreground hover:text-foreground border border-transparent hover:border-border hover:bg-muted/50'
? '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'
}
`}
>
<ArrowDown className="w-3 h-3" />
Auto-scroll
{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>
</button>
</div>
{/* Log content */}
<div className="flex-1 overflow-y-auto p-4 custom-scrollbar bg-background/50">
<div className="flex-1 overflow-y-auto p-5 custom-scrollbar bg-gradient-to-b from-background/80 to-background/40">
{/* Filter indicator */}
{selectedAgentId && !showAllLogs && (
<div className="mb-3 px-3 py-2 bg-primary/8 border border-primary/25 rounded flex items-center justify-between">
<div className="flex items-center gap-2 text-xs text-primary">
<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="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" />
<span className="tracking-wide">Filtering logs for selected agent</span>
</div>
<span className="tracking-wide font-medium">Filtering logs for selected agent</span>
</div>
<button
onClick={() => selectAgent(null)}
className="text-xs text-muted-foreground hover:text-foreground transition-colors font-mono uppercase tracking-wider"
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"
>
Clear
Clear Filter
</button>
</div>
)}
@ -885,98 +906,131 @@ function AgentAuditPageContent() {
{/* Status bar */}
{task && (
<div className="flex-shrink-0 h-9 border-t border-border flex items-center justify-between px-4 text-xs cyber-bg-elevated/90 backdrop-blur-sm">
<span>
<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">
{/* Progress bar background */}
<div
className="absolute inset-0 bg-gradient-to-r from-primary/10 to-transparent transition-all duration-700 ease-out"
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 text-emerald-400">
<span className="relative flex h-1.5 w-1.5">
<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-1.5 w-1.5 bg-emerald-400 shadow-[0_0_6px_rgba(61,214,140,0.5)]"></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">{statusVerb}{'.'.repeat(statusDots)}</span>
<span className="font-mono tracking-wide font-semibold">{statusVerb}{'.'.repeat(statusDots)}</span>
</span>
) : isComplete ? (
<span className="text-muted-foreground font-mono tracking-wide">AUDIT {task.status?.toUpperCase()}</span>
<span className="flex items-center gap-2 text-muted-foreground font-mono tracking-wide">
<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>
<div className="flex items-center gap-4 font-mono text-muted-foreground">
<span>
<span className="text-primary text-glow-primary">{task.progress_percentage?.toFixed(0) || 0}</span>
<span className="text-muted-foreground">%</span>
</span>
<span className="text-border"></span>
<span>
<span className="text-foreground">{task.analyzed_files}</span>
<span className="text-muted-foreground">/{task.total_files} files</span>
</span>
<span className="text-border"></span>
<span>
<span className="text-foreground">{task.tool_calls_count || 0}</span>
<span className="text-muted-foreground"> tools</span>
</span>
<div className="flex items-center gap-5 font-mono text-muted-foreground relative z-10">
<div className="flex items-center gap-1.5">
<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="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>
</div>
<div className="w-px h-4 bg-border/50" />
<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>
</div>
</div>
</div>
)}
</div>
{/* Right Panel - Agent Tree + Stats */}
<div className="w-1/4 flex flex-col bg-background">
<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" />
{/* Agent Tree section */}
<div className="flex-1 flex flex-col border-b border-border overflow-hidden">
<div className="flex-1 flex flex-col border-b border-border/50 overflow-hidden">
{/* Tree header */}
<div className="flex-shrink-0 h-11 border-b border-border flex items-center justify-between px-4 cyber-bg-elevated/90">
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<Bot className="w-4 h-4 text-muted-foreground" />
<span className="uppercase font-bold tracking-[0.15em] text-foreground">Agent Tree</span>
<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-1.5 text-xs border-border text-muted-foreground font-mono bg-muted">
<Badge variant="outline" className="h-5 px-2 text-xs border-violet-500/30 text-violet-500 font-mono bg-violet-500/10">
{agentTree.total_agents}
</Badge>
)}
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 relative z-10">
{selectedAgentId && !showAllLogs && (
<button
onClick={() => selectAgent(null)}
className="text-xs text-primary hover:text-primary/80 transition-colors font-mono uppercase tracking-wider"
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"
>
Show All
</button>
)}
{agentTree && agentTree.running_agents > 0 && (
<div className="flex items-center gap-1.5 text-emerald-400">
<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(61,214,140,0.5)]"></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">{agentTree.running_agents}</span>
<span className="text-xs font-mono text-emerald-400 font-semibold">{agentTree.running_agents}</span>
</div>
)}
</div>
</div>
{/* Tree content */}
<div className="flex-1 overflow-y-auto p-2 custom-scrollbar bg-background/50">
<div className="flex-1 overflow-y-auto p-3 custom-scrollbar bg-gradient-to-b from-background/60 to-background/30">
{treeNodes.length > 0 ? (
treeNodes.map(node => (
<div className="space-y-0.5">
{treeNodes.map(node => (
<AgentTreeNodeItem
key={node.agent_id}
node={node}
selectedId={selectedAgentId}
onSelect={handleAgentSelect}
/>
))
))}
</div>
) : (
<div className="h-full flex items-center justify-center text-muted-foreground text-xs">
{isRunning ? (
<div className="flex items-center gap-2">
<Loader2 className="w-3 h-3 animate-spin" />
<span className="font-mono tracking-wide">INITIALIZING AGENTS...</span>
<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>
</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>
</div>
)}
</div>
)}
@ -984,7 +1038,7 @@ function AgentAuditPageContent() {
</div>
{/* Bottom section - Details + Stats */}
<div className="flex-shrink-0 p-3 space-y-3">
<div className="flex-shrink-0 p-4 space-y-3 bg-card/50 backdrop-blur-sm">
{/* Agent detail panel */}
{selectedAgentId && !showAllLogs && (
<AgentDetailPanel

View File

@ -60,7 +60,7 @@ export default function Login() {
localStorage.removeItem("remembered_email");
}
await login(response.data.access_token);
await login(response.data.access_token, rememberMe);
toast.success("登录成功");
} catch (error: any) {
const detail = error.response?.data?.detail;

View File

@ -13,7 +13,8 @@ export const apiClient = axios.create({
// Request interceptor to add token
apiClient.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem('access_token');
// Check both localStorage (remember me) and sessionStorage (session only)
const token = localStorage.getItem('access_token') || sessionStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
@ -29,6 +30,7 @@ apiClient.interceptors.response.use(
if (error.response?.status === 401) {
// Auto logout if token is invalid or expired
localStorage.removeItem('access_token');
sessionStorage.removeItem('access_token');
// Redirect to login
window.location.href = '/login';
}

View File

@ -13,7 +13,7 @@ interface AuthContextType {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
login: (token: string) => Promise<void>;
login: (token: string, rememberMe?: boolean) => Promise<void>;
logout: () => void;
}
@ -26,7 +26,8 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
useEffect(() => {
const checkAuth = async () => {
const token = localStorage.getItem('access_token');
// Check both localStorage (remember me) and sessionStorage (session only)
const token = localStorage.getItem('access_token') || sessionStorage.getItem('access_token');
if (token) {
try {
const response = await apiClient.get('/users/me');
@ -43,8 +44,18 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
checkAuth();
}, []);
const login = async (token: string) => {
const login = async (token: string, rememberMe: boolean = false) => {
// Clear any existing tokens first
localStorage.removeItem('access_token');
sessionStorage.removeItem('access_token');
// Store token based on rememberMe preference
if (rememberMe) {
localStorage.setItem('access_token', token);
} else {
sessionStorage.setItem('access_token', token);
}
try {
const response = await apiClient.get('/users/me');
setUser(response.data);
@ -56,6 +67,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
const logout = () => {
localStorage.removeItem('access_token');
sessionStorage.removeItem('access_token');
setUser(null);
setIsAuthenticated(false);
};

View File

@ -20,8 +20,8 @@ export default {
extend: {
// Typography - Pixel-perfect monospace for terminal aesthetic
fontFamily: {
mono: ['"ArkPixel"', '"JetBrains Mono"', '"Roboto Mono"', '"Courier New"', 'monospace'],
sans: ['"Inter"', 'system-ui', 'sans-serif'],
mono: ['"ArkPixel"', '"CJK Fallback"', '"Noto Sans SC"', '"PingFang SC"', '"Microsoft YaHei"', '"JetBrains Mono"', '"Roboto Mono"', '"Courier New"', 'monospace'],
sans: ['"Inter"', '"Noto Sans SC"', '"PingFang SC"', '"Microsoft YaHei"', 'system-ui', 'sans-serif'],
display: ['"Orbitron"', '"Rajdhani"', 'sans-serif'],
},
fontSize: {