Spaces:
Runtime error
Runtime error
| 'use client'; | |
| import React, { useState, useEffect } from 'react'; | |
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; | |
| import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; | |
| import { Badge } from '@/components/ui/badge'; | |
| import { Button } from '@/components/ui/button'; | |
| import { | |
| Brain, | |
| Activity, | |
| Rocket, | |
| BarChart3, | |
| Zap, | |
| CheckCircle, | |
| AlertCircle, | |
| TrendingUp, | |
| Clock, | |
| Cpu, | |
| RefreshCw, | |
| ArrowRight, | |
| Play, | |
| Settings, | |
| Eye, | |
| Download | |
| } from 'lucide-react'; | |
| import AIArchitectPanel from './AIArchitectPanel'; | |
| import BackendStatusIndicator from './BackendStatusIndicator'; | |
| interface AIArchitectStats { | |
| total_workflows: number; | |
| completed_workflows: number; | |
| failed_workflows: number; | |
| running_workflows: number; | |
| success_rate: number; | |
| average_consciousness_score: number; | |
| domain_distribution: Record<string, number>; | |
| system_status: string; | |
| architect_initialized: boolean; | |
| } | |
| interface RealtimeMetrics { | |
| requests_last_hour: number; | |
| success_rate_last_hour: number; | |
| avg_consciousness_last_hour: number; | |
| active_domains_last_hour: number; | |
| total_requests_today: number; | |
| timestamp: string; | |
| } | |
| export default function EnhancedDashboard() { | |
| const [activeTab, setActiveTab] = useState('architect'); | |
| const [aiStats, setAiStats] = useState<AIArchitectStats | null>(null); | |
| const [realtimeMetrics, setRealtimeMetrics] = useState<RealtimeMetrics | null>(null); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [lastRefresh, setLastRefresh] = useState(new Date()); | |
| const [isClient, setIsClient] = useState(false); | |
| // Mock quick templates | |
| const quickTemplates = [ | |
| { | |
| name: 'Image Classification CNN', | |
| description: 'CNN with attention mechanisms for image classification', | |
| consciousness: 92, | |
| prompt: 'Create a deep learning image classification system using CNN with attention mechanisms for high accuracy image recognition' | |
| }, | |
| { | |
| name: 'Transformer for NLP', | |
| description: 'BERT-based transformer for natural language processing', | |
| consciousness: 88, | |
| prompt: 'Build a BERT-based transformer model for natural language processing tasks like sentiment analysis and text classification' | |
| }, | |
| { | |
| name: 'Time Series LSTM', | |
| description: 'LSTM network for time series prediction', | |
| consciousness: 85, | |
| prompt: 'Create an LSTM neural network for time series forecasting and prediction with feature engineering' | |
| }, | |
| { | |
| name: 'Multimodal Fusion', | |
| description: 'Combines text and image processing', | |
| consciousness: 94, | |
| prompt: 'Build a multimodal AI system that combines computer vision and natural language processing for comprehensive analysis' | |
| } | |
| ]; | |
| // Smart API base detection with fallbacks | |
| const getApiBase = () => { | |
| if (typeof window === 'undefined') return process.env.NEXT_PUBLIC_TRAINING_API_BASE || 'http://localhost:9006'; | |
| const fallbacks = [ | |
| process.env.NEXT_PUBLIC_TRAINING_API_BASE, | |
| 'http://localhost:9006', | |
| 'http://127.0.0.1:9006', | |
| 'http://0.0.0.0:9006' | |
| ].filter(Boolean) as string[]; | |
| return fallbacks[0]; // Use first fallback for now | |
| }; | |
| // duplicate smartFetch removed (using single robust smartFetch below) | |
| // Function to handle quick template generation | |
| const API_BASE = getApiBase(); | |
| // Robust fetch that tries alternative backends if the primary base is unreachable | |
| const smartFetch = async (path: string, init?: RequestInit) => { | |
| const candidates = [ | |
| API_BASE, | |
| 'http://127.0.0.1:9006', | |
| (typeof window !== 'undefined' && window.location ? window.location.origin.replace(':9002', ':9006') : ''), | |
| 'http://0.0.0.0:9006' | |
| ].filter(Boolean) as string[]; | |
| let lastErr: any = null; | |
| for (const base of candidates) { | |
| try { | |
| const res = await fetch(`${base}${path}`, init); | |
| if (res.ok || res.status < 500) return res; | |
| lastErr = new Error(`HTTP ${res.status}`); | |
| } catch (e) { | |
| lastErr = e; | |
| } | |
| } | |
| throw lastErr || new Error('Failed to fetch'); | |
| }; | |
| const handleQuickGeneration = async (template: any) => { | |
| try { | |
| setIsLoading(true); | |
| console.log(`🚀 Generating quick template: ${template.name}`); | |
| // Call the real AI architect with the template prompt | |
| const controller = new AbortController(); | |
| const timeout = setTimeout(() => controller.abort(), 6000); | |
| const response = await fetch(`${API_BASE}/api/ai-architect/process-prompt`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' }, | |
| body: JSON.stringify({ prompt: template.prompt }), | |
| signal: controller.signal | |
| }); | |
| clearTimeout(timeout); | |
| if (response.ok) { | |
| const result = await response.json(); | |
| console.log('✅ Quick template generated successfully!', result); | |
| // Switch to architect tab to show results | |
| setActiveTab('architect'); | |
| // Refresh stats to show the new workflow | |
| setTimeout(() => { | |
| fetchAIStats(); | |
| fetchRealtimeMetrics(); | |
| }, 1000); | |
| } else { | |
| console.error('❌ Quick template generation failed'); | |
| } | |
| } catch (error) { | |
| console.error('❌ Quick template generation error:', error); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| useEffect(() => { | |
| setIsClient(true); | |
| fetchAIStats(); | |
| fetchRealtimeMetrics(); | |
| // Set up periodic refresh | |
| const interval = setInterval(() => { | |
| fetchRealtimeMetrics(); | |
| }, 30000); // Refresh every 30 seconds | |
| return () => clearInterval(interval); | |
| }, []); | |
| const fetchAIStats = async () => { | |
| try { | |
| setIsLoading(true); | |
| // Call REAL AI Architect statistics endpoint | |
| const response = await smartFetch(`/api/ai-architect/statistics`, { | |
| headers: { 'Cache-Control': 'no-cache' }, | |
| cache: 'no-store' | |
| }); | |
| if (response.ok) { | |
| const stats = await response.json(); | |
| // Map the real backend data to frontend structure | |
| setAiStats({ | |
| total_workflows: stats.total_workflows || 0, | |
| completed_workflows: stats.completed_workflows || 0, | |
| failed_workflows: stats.failed_workflows || 0, | |
| running_workflows: stats.running_workflows || 0, | |
| success_rate: stats.success_rate || 0, | |
| average_consciousness_score: stats.average_consciousness_score * 100 || 0, // Convert to percentage | |
| domain_distribution: stats.domain_distribution || {}, | |
| system_status: stats.system_status || 'Unknown', | |
| architect_initialized: stats.architect_initialized || false | |
| }); | |
| console.log('✅ Loaded REAL AI Architect statistics:', stats); | |
| } else { | |
| console.warn('⚠️ Failed to fetch real stats, using fallback'); | |
| // Fallback data only if API fails | |
| setAiStats({ | |
| total_workflows: 0, | |
| completed_workflows: 0, | |
| failed_workflows: 0, | |
| running_workflows: 0, | |
| success_rate: 0, | |
| average_consciousness_score: 0, | |
| domain_distribution: {}, | |
| system_status: 'Connecting...', | |
| architect_initialized: false | |
| }); | |
| } | |
| } catch (error) { | |
| console.error('❌ Failed to fetch AI stats:', error); | |
| // Fallback data on error | |
| setAiStats({ | |
| total_workflows: 0, | |
| completed_workflows: 0, | |
| failed_workflows: 0, | |
| running_workflows: 0, | |
| success_rate: 0, | |
| average_consciousness_score: 0, | |
| domain_distribution: {}, | |
| system_status: 'Connection Error', | |
| architect_initialized: false | |
| }); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| const fetchRealtimeMetrics = async () => { | |
| try { | |
| // Call REAL MLE-STAR status endpoint for live metrics | |
| const [mleResponse, workflowResponse] = await Promise.all([ | |
| smartFetch('/api/mle-star/status', { cache: 'no-store' }), | |
| smartFetch('/api/workflow-status/all', { cache: 'no-store' }) | |
| ]); | |
| let realTimeData = { | |
| requests_last_hour: 0, | |
| success_rate_last_hour: 0, | |
| avg_consciousness_last_hour: 0, | |
| active_domains_last_hour: 0, | |
| total_requests_today: 0, | |
| timestamp: new Date().toISOString() | |
| }; | |
| if (mleResponse.ok) { | |
| const mleStatus = await mleResponse.json(); | |
| console.log('✅ Got MLE-STAR status:', mleStatus); | |
| // Extract real metrics from MLE-STAR backend | |
| if (mleStatus.status === 'active' && mleStatus.metrics) { | |
| realTimeData.requests_last_hour = mleStatus.metrics.requests_last_hour || 0; | |
| realTimeData.success_rate_last_hour = mleStatus.metrics.success_rate || 0; | |
| realTimeData.avg_consciousness_last_hour = mleStatus.metrics.consciousness_level * 100 || 0; | |
| } | |
| } | |
| if (workflowResponse.ok) { | |
| const workflows = await workflowResponse.json(); | |
| console.log('✅ Got workflow status:', workflows); | |
| // Count active workflows and calculate metrics | |
| if (Array.isArray(workflows)) { | |
| const activeWorkflows = workflows.filter(w => w.status === 'running' || w.status === 'processing'); | |
| const completedToday = workflows.filter(w => { | |
| const workflowDate = new Date(w.created_at || w.timestamp); | |
| const today = new Date(); | |
| return workflowDate.toDateString() === today.toDateString(); | |
| }); | |
| realTimeData.active_domains_last_hour = new Set(workflows.map(w => w.domain)).size; | |
| realTimeData.total_requests_today = completedToday.length; | |
| } | |
| } | |
| setRealtimeMetrics(realTimeData); | |
| console.log('📊 Updated real-time metrics:', realTimeData); | |
| } catch (error) { | |
| console.error('❌ Failed to fetch realtime metrics:', error); | |
| // Keep existing metrics or set defaults | |
| if (!realtimeMetrics) { | |
| setRealtimeMetrics({ | |
| requests_last_hour: 0, | |
| success_rate_last_hour: 0, | |
| avg_consciousness_last_hour: 0, | |
| active_domains_last_hour: 0, | |
| total_requests_today: 0, | |
| timestamp: new Date().toISOString() | |
| }); | |
| } | |
| } | |
| }; | |
| const manualRefresh = () => { | |
| setIsLoading(true); | |
| fetchAIStats(); | |
| fetchRealtimeMetrics(); | |
| setLastRefresh(new Date()); | |
| setTimeout(() => setIsLoading(false), 1000); | |
| }; | |
| const getSystemStatusBadge = () => { | |
| if (!aiStats) return ( | |
| <Badge className="bg-yellow-500/20 text-yellow-400 border-yellow-500/30"> | |
| <AlertCircle className="w-3 h-3 mr-1" /> | |
| Loading... | |
| </Badge> | |
| ); | |
| const status = aiStats.system_status; | |
| const isReady = aiStats.architect_initialized; | |
| if (isReady && (status === 'operational' || status === 'active')) { | |
| return ( | |
| <Badge className="bg-[#39ff14]/20 text-[#39ff14] border-[#39ff14]/30"> | |
| <CheckCircle className="w-3 h-3 mr-1" /> | |
| Online & Ready | |
| </Badge> | |
| ); | |
| } else if (status === 'Connecting...' || status === 'Initializing') { | |
| return ( | |
| <Badge className="bg-blue-500/20 text-blue-400 border-blue-500/30"> | |
| <RefreshCw className="w-3 h-3 mr-1 animate-spin" /> | |
| {status} | |
| </Badge> | |
| ); | |
| } else if (status === 'Connection Error') { | |
| return ( | |
| <Badge className="bg-red-500/20 text-red-400 border-red-500/30"> | |
| <AlertCircle className="w-3 h-3 mr-1" /> | |
| Connection Error | |
| </Badge> | |
| ); | |
| } else { | |
| return ( | |
| <Badge className="bg-yellow-500/20 text-yellow-400 border-yellow-500/30"> | |
| <AlertCircle className="w-3 h-3 mr-1" /> | |
| {status} | |
| </Badge> | |
| ); | |
| } | |
| }; | |
| return ( | |
| <div className="h-screen w-full flex flex-col bg-black/95 relative overflow-hidden"> | |
| {/* Neon grid overlay */} | |
| <div className="absolute inset-0 pointer-events-none z-0"> | |
| <div className="instrument-grid-overlay w-full h-full" /> | |
| </div> | |
| {/* Custom scrollbar styles */} | |
| <style dangerouslySetInnerHTML={{ | |
| __html: ` | |
| .cyber-scrollbar { | |
| scrollbar-width: thin; | |
| scrollbar-color: #00ffe7 rgba(0, 255, 231, 0.1); | |
| } | |
| .cyber-scrollbar::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| .cyber-scrollbar::-webkit-scrollbar-track { | |
| background: rgba(0, 0, 0, 0.3); | |
| border-radius: 4px; | |
| } | |
| .cyber-scrollbar::-webkit-scrollbar-thumb { | |
| background: linear-gradient(180deg, #00ffe7, #00b8a3); | |
| border-radius: 4px; | |
| border: 1px solid rgba(0, 255, 231, 0.3); | |
| } | |
| .cyber-scrollbar::-webkit-scrollbar-thumb:hover { | |
| background: linear-gradient(180deg, #39ff14, #00ffe7); | |
| box-shadow: 0 0 8px rgba(0, 255, 231, 0.5); | |
| } | |
| .cyber-scrollbar::-webkit-scrollbar-corner { | |
| background: rgba(0, 0, 0, 0.3); | |
| } | |
| ` | |
| }} /> | |
| {/* Header */} | |
| <div className="w-full p-4 z-10 flex-shrink-0"> | |
| <Card className="bg-black/80 border-[#00ffe7]/30 shadow-[#00ffe7]/20"> | |
| <CardHeader className="py-3"> | |
| <div className="flex items-center justify-between"> | |
| <div className="flex items-center gap-3"> | |
| <Brain className="h-8 w-8 text-[#00ffe7] animate-pulse" /> | |
| <div> | |
| <CardTitle className="text-2xl font-mono tracking-tight text-[#00ffe7]"> | |
| ZPE AI Architect V2 | |
| </CardTitle> | |
| <CardDescription className="text-sm text-[#00ffe7]/80"> | |
| Ultimate Workflow Automator • {aiStats?.total_workflows || 0} workflows processed • {aiStats?.success_rate?.toFixed(1) || '0.0'}% success rate | |
| </CardDescription> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| <Badge className="bg-[#00ffe7]/20 text-[#00ffe7] border-[#00ffe7]/30"> | |
| Dataset Architect Integration Active | |
| </Badge> | |
| {getSystemStatusBadge()} | |
| <BackendStatusIndicator /> | |
| <Button | |
| size="sm" | |
| onClick={manualRefresh} | |
| disabled={isLoading} | |
| className="bg-[#00ffe7]/20 text-[#00ffe7] border border-[#00ffe7]/30 hover:bg-[#00ffe7]/30 hover:text-white" | |
| > | |
| <RefreshCw className={`w-4 h-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} /> | |
| Refresh | |
| </Button> | |
| </div> | |
| </div> | |
| </CardHeader> | |
| </Card> | |
| </div> | |
| {/* Main Content */} | |
| <div className="flex-1 flex flex-col p-4 pt-0 z-10 min-h-0"> | |
| <Card className="bg-black/80 border-[#00ffe7]/30 flex-1 min-h-0"> | |
| <CardContent className="p-0 h-full min-h-0"> | |
| <Tabs value={activeTab} onValueChange={setActiveTab} className="h-full flex flex-col"> | |
| <div className="border-b border-[#00ffe7]/20"> | |
| <TabsList className="grid w-full grid-cols-4 bg-transparent border-0 h-12"> | |
| <TabsTrigger | |
| value="architect" | |
| className="flex items-center space-x-2 data-[state=active]:bg-[#00ffe7]/20 data-[state=active]:text-[#00ffe7] text-[#00ffe7]/60 border-0" | |
| > | |
| <Brain className="w-4 h-4" /> | |
| <span>AI Architect</span> | |
| </TabsTrigger> | |
| <TabsTrigger | |
| value="workflows" | |
| className="flex items-center space-x-2 data-[state=active]:bg-[#00ffe7]/20 data-[state=active]:text-[#00ffe7] text-[#00ffe7]/60 border-0" | |
| > | |
| <Activity className="w-4 h-4" /> | |
| <span>Workflows</span> | |
| </TabsTrigger> | |
| <TabsTrigger | |
| value="templates" | |
| className="flex items-center space-x-2 data-[state=active]:bg-[#00ffe7]/20 data-[state=active]:text-[#00ffe7] text-[#00ffe7]/60 border-0" | |
| > | |
| <Rocket className="w-4 h-4" /> | |
| <span>Quick Start</span> | |
| </TabsTrigger> | |
| <TabsTrigger | |
| value="analytics" | |
| className="flex items-center space-x-2 data-[state=active]:bg-[#00ffe7]/20 data-[state=active]:text-[#00ffe7] text-[#00ffe7]/60 border-0" | |
| > | |
| <BarChart3 className="w-4 h-4" /> | |
| <span>Analytics</span> | |
| </TabsTrigger> | |
| </TabsList> | |
| </div> | |
| <div className="flex-1 min-h-0 p-4 overflow-y-auto cyber-scrollbar"> | |
| <TabsContent value="architect" className="h-full mt-0 overflow-y-auto cyber-scrollbar"> | |
| <AIArchitectPanel /> | |
| </TabsContent> | |
| <TabsContent value="workflows" className="h-full mt-0 overflow-y-auto cyber-scrollbar"> | |
| <div className="p-8 text-center text-[#00ffe7]/60"> | |
| <Activity className="w-16 h-16 mx-auto mb-4" /> | |
| <h3 className="text-lg font-medium mb-2">Workflow Monitor</h3> | |
| <p>Real-time monitoring of automated AI workflows</p> | |
| </div> | |
| </TabsContent> | |
| <TabsContent value="templates" className="h-full mt-0 overflow-y-auto cyber-scrollbar"> | |
| <div className="space-y-4 h-full"> | |
| <div className="flex items-center justify-between"> | |
| <div> | |
| <h3 className="text-lg font-mono text-[#00ffe7]">Quick Start Templates</h3> | |
| <p className="text-sm text-[#00ffe7]/70">Pre-configured architectures with consciousness enhancement</p> | |
| </div> | |
| <Button | |
| size="sm" | |
| className="bg-[#00ffe7]/20 text-[#00ffe7] border border-[#00ffe7]/30 hover:bg-[#00ffe7]/30 hover:text-white" | |
| onClick={() => { | |
| // Switch to architect tab for full generation | |
| setActiveTab('architect'); | |
| }} | |
| disabled={isLoading} | |
| > | |
| <Play className="w-4 h-4 mr-2" /> | |
| {isLoading ? 'Processing...' : 'Quick Generation'} | |
| </Button> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| {quickTemplates.map((template) => ( | |
| <Card key={template.name} className="bg-black/60 border-[#00ffe7]/20 hover:border-[#00ffe7]/40 transition-colors"> | |
| <CardHeader className="pb-2"> | |
| <div className="flex items-center justify-between"> | |
| <CardTitle className="text-sm text-[#00ffe7]">{template.name}</CardTitle> | |
| <Badge className="bg-[#39ff14]/20 text-[#39ff14] border-[#39ff14]/30 text-xs"> | |
| {template.consciousness}% Consciousness | |
| </Badge> | |
| </div> | |
| <CardDescription className="text-xs text-[#00ffe7]/60"> | |
| {template.description} | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="pt-0"> | |
| <Button | |
| size="sm" | |
| className="w-full bg-[#00ffe7]/20 text-[#00ffe7] border border-[#00ffe7]/30 hover:bg-[#00ffe7]/30 hover:text-white" | |
| onClick={() => handleQuickGeneration(template)} | |
| > | |
| Generate | |
| <ArrowRight className="w-3 h-3 ml-2" /> | |
| </Button> | |
| </CardContent> | |
| </Card> | |
| ))} | |
| </div> | |
| </div> | |
| </TabsContent> | |
| <TabsContent value="analytics" className="h-full mt-0 overflow-y-auto cyber-scrollbar"> | |
| <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6"> | |
| {/* Metrics Cards */} | |
| <Card className="bg-black/60 border-[#00ffe7]/20"> | |
| <CardHeader className="pb-2"> | |
| <div className="flex items-center justify-between"> | |
| <CardTitle className="text-sm text-[#00ffe7]">Total Workflows</CardTitle> | |
| <Activity className="h-4 w-4 text-[#00ffe7]" /> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-mono text-[#00ffe7]"> | |
| {aiStats?.total_workflows || 0} | |
| </div> | |
| <p className="text-xs text-[#00ffe7]/60"> | |
| All time processed | |
| </p> | |
| </CardContent> | |
| </Card> | |
| <Card className="bg-black/60 border-[#00ffe7]/20"> | |
| <CardHeader className="pb-2"> | |
| <div className="flex items-center justify-between"> | |
| <CardTitle className="text-sm text-[#00ffe7]">Success Rate</CardTitle> | |
| <TrendingUp className="h-4 w-4 text-[#39ff14]" /> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-mono text-[#39ff14]"> | |
| {aiStats?.success_rate || 0}% | |
| </div> | |
| <p className="text-xs text-[#00ffe7]/60"> | |
| Completion rate | |
| </p> | |
| </CardContent> | |
| </Card> | |
| <Card className="bg-black/60 border-[#00ffe7]/20"> | |
| <CardHeader className="pb-2"> | |
| <div className="flex items-center justify-between"> | |
| <CardTitle className="text-sm text-[#00ffe7]">Consciousness</CardTitle> | |
| <Brain className="h-4 w-4 text-[#00ffe7]" /> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-mono text-[#00ffe7]"> | |
| {aiStats?.average_consciousness_score || 0}% | |
| </div> | |
| <p className="text-xs text-[#00ffe7]/60"> | |
| Average score | |
| </p> | |
| </CardContent> | |
| </Card> | |
| <Card className="bg-black/60 border-[#00ffe7]/20"> | |
| <CardHeader className="pb-2"> | |
| <div className="flex items-center justify-between"> | |
| <CardTitle className="text-sm text-[#00ffe7]">Active Jobs</CardTitle> | |
| <Cpu className="h-4 w-4 text-yellow-400" /> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="text-2xl font-mono text-yellow-400"> | |
| {aiStats?.running_workflows || 0} | |
| </div> | |
| <p className="text-xs text-[#00ffe7]/60"> | |
| Currently running | |
| </p> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| {/* Domain Distribution */} | |
| {aiStats?.domain_distribution && ( | |
| <Card className="bg-black/60 border-[#00ffe7]/20"> | |
| <CardHeader className="pb-3"> | |
| <CardTitle className="text-[#00ffe7] flex items-center gap-2"> | |
| <BarChart3 className="h-5 w-5" /> | |
| Domain Distribution | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="space-y-3"> | |
| {Object.entries(aiStats.domain_distribution).map(([domain, count]) => ( | |
| <div key={domain} className="flex items-center justify-between"> | |
| <span className="text-sm text-[#00ffe7]/80">{domain}</span> | |
| <div className="flex items-center gap-2"> | |
| <div className="w-24 h-2 bg-black/40 rounded-full overflow-hidden"> | |
| <div | |
| className="h-full bg-[#00ffe7] rounded-full transition-all duration-500" | |
| style={{ width: `${(count / Math.max(...Object.values(aiStats.domain_distribution))) * 100}%` }} | |
| /> | |
| </div> | |
| <span className="text-sm font-mono text-[#00ffe7] w-8 text-right">{count}</span> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </TabsContent> | |
| </div> | |
| </Tabs> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| </div> | |
| ); | |
| } |