Last active
April 19, 2026 00:20
-
-
Save chaotic3quilibrium/bfaee0aefaeab675ab91ee8a6553c2e1 to your computer and use it in GitHub Desktop.
LLM TCO Analyzer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useState, useMemo } from 'react'; | |
| import { | |
| Activity, | |
| Server, | |
| Cloud, | |
| Settings, | |
| Info, | |
| BarChart3, | |
| CheckCircle2, | |
| Trophy, | |
| X, | |
| Plus, | |
| ArrowRight, | |
| PlusCircle, | |
| Layers, | |
| ShieldCheck, | |
| User, | |
| History, | |
| Box, | |
| Terminal, | |
| Globe, | |
| Image as ImageIcon, | |
| Music, | |
| Film, | |
| Network, | |
| BrainCircuit, | |
| CreditCard | |
| } from 'lucide-react'; | |
| // --- Type Definitions --- | |
| interface Task { | |
| label: string; | |
| description: string; | |
| intent: string; | |
| reasoningIntensity: number; | |
| } | |
| interface AppConfig { | |
| label: string; | |
| icon: React.ElementType; | |
| tokens: number; | |
| ratio: number; | |
| certified: string[]; | |
| description: string; | |
| } | |
| interface UsageState { | |
| tokensPerDay: number; | |
| ioRatio: number; | |
| electricityRate: number; | |
| pue: number; | |
| analysisMonths: number; | |
| } | |
| interface UtilityScores { | |
| coding: number; | |
| reasoning: number; | |
| creative: number; | |
| extraction: number; | |
| [key: string]: number; | |
| } | |
| interface HardwareConfig { | |
| id: string; | |
| name: string; | |
| hardwareCost: number; | |
| basePowerWatts: number; | |
| loadPowerWatts: number; | |
| hoursPerDay: number; | |
| maintenanceCostMonthly: number; | |
| quantization: string; | |
| vram: number; | |
| context: string; | |
| quantity: number; | |
| fabric: string; | |
| harness: string; | |
| thinkingOverhead?: number; | |
| enabled: boolean; | |
| type: 'local'; | |
| isCustom: boolean; | |
| pros: string[]; | |
| cons: string[]; | |
| utility?: number | UtilityScores; | |
| } | |
| interface CloudProvider { | |
| id: string; | |
| name: string; | |
| inputPrice?: number; | |
| outputPrice?: number; | |
| thinkingMultiplier?: number; | |
| tier: string; | |
| context: string; | |
| type: 'cloud'; | |
| isCustom: boolean; | |
| utility: UtilityScores; | |
| pros: string[]; | |
| cons: string[]; | |
| isSubscription?: boolean; | |
| flatMonthly?: number; | |
| quota?: number; | |
| } | |
| interface CalculatedCloud extends CloudProvider { | |
| monthlyInputCost: number; | |
| monthlyOutputCost: number; | |
| monthlyThinkingCost: number; | |
| thinkingTokens: number; | |
| monthlyCost: number; | |
| totalCost: number; | |
| isCertified: boolean; | |
| valueScore: number; | |
| isSelected: boolean; | |
| isOverQuota: boolean; | |
| } | |
| interface CalculatedLocal extends HardwareConfig { | |
| totalVram: number; | |
| monthlyEnergy: number; | |
| monthlyOpex: number; | |
| totalCost: number; | |
| amortizedMonthly: number; | |
| utility: number; | |
| vramConflict: boolean; | |
| valueScore: number; | |
| isSelected: boolean; | |
| scaledHardwareCost: number; | |
| thinkingIntensity: number; | |
| } | |
| type DetailItem = CalculatedCloud | CalculatedLocal; | |
| // --- April 2026 Task Definitions --- | |
| const TASKS: Record<string, Task> = { | |
| coding: { | |
| label: 'Software Engineering', | |
| description: 'Agentic multi-file edits and architectural reasoning.', | |
| intent: 'Optimized for 100k+ line context and strict type adherence.', | |
| reasoningIntensity: 1.5 | |
| }, | |
| reasoning: { | |
| label: 'Deep Thinking', | |
| description: 'Internal chain-of-thought (CoT) for math, logic, and scientific planning.', | |
| intent: 'Prioritizes logical consistency over speed. Ideal for multi-step verification.', | |
| reasoningIntensity: 4.0 | |
| }, | |
| creative: { | |
| label: 'Creative/Context', | |
| description: 'Style fidelity and long-form narrative coherence.', | |
| intent: 'Focuses on stylistic variety and narrative consistency.', | |
| reasoningIntensity: 0.8 | |
| }, | |
| extraction: { | |
| label: 'Schema Automation', | |
| description: 'Ultra-high volume JSON extraction and cleanup.', | |
| intent: 'Optimized for zero-shot JSON schema adherence and high precision.', | |
| reasoningIntensity: 0.2 | |
| } | |
| }; | |
| // --- April 2026 Application Ecosystems --- | |
| const APPS: Record<string, AppConfig> = { | |
| standard: { label: 'Generic API', icon: Globe, tokens: 1000000, ratio: 0.3, description: "Standard direct API usage pattern.", certified: [] }, | |
| claudeCode: { | |
| label: 'Claude-Code (CLI)', | |
| icon: Terminal, | |
| tokens: 4500000, | |
| ratio: 0.85, | |
| certified: ['claude_4_5_sonnet', 'gemini_3_1_pro'], | |
| description: "High-volume input for repo indexing. Requires massive context." | |
| }, | |
| nanoBanana: { | |
| label: 'NanoBanana (Vision)', | |
| icon: ImageIcon, | |
| tokens: 3000000, | |
| ratio: 0.2, | |
| certified: ['gemini_3_1_pro', 'claude_4_5_opus'], | |
| description: "Vision-centric design agent. Heavy output for asset generation." | |
| }, | |
| antiGravity: { | |
| label: 'Google AntiGravity', | |
| icon: Box, | |
| tokens: 2500000, | |
| ratio: 0.5, | |
| certified: ['gemini_3_1_pro', 'gemini_3_flash'], | |
| description: "Agentic OS layer. Balanced I/O with heavy reasoning usage." | |
| }, | |
| sonicAgent: { | |
| label: 'Sonic Agent (Audio)', | |
| icon: Music, | |
| tokens: 1800000, | |
| ratio: 0.4, | |
| certified: ['gpt5_4_pro', 'claude_4_5_opus'], | |
| description: "Music composition. High reasoning overhead for temporal logic." | |
| }, | |
| motionSora: { | |
| label: 'MotionSora (Video)', | |
| icon: Film, | |
| tokens: 15000000, | |
| ratio: 0.1, | |
| certified: ['gemini_3_1_pro', 'gpt5_4_pro'], | |
| description: "Video generation. Peak token consumption for frame density." | |
| }, | |
| cowork: { | |
| label: 'Anthropic CoWork', | |
| icon: Layers, | |
| tokens: 8000000, | |
| ratio: 0.7, | |
| certified: ['claude_4_5_opus', 'gpt5_4_pro'], | |
| description: "Enterprise agent swarm. Intense token density." | |
| } | |
| }; | |
| const App: React.FC = () => { | |
| // --- Global State --- | |
| const [activeTask, setActiveTask] = useState<string>('coding'); | |
| const [activeApp, setActiveApp] = useState<string>('standard'); | |
| const [detailItem, setDetailItem] = useState<DetailItem | null>(null); | |
| const [showAddModal, setShowAddModal] = useState<'cloud' | 'local' | null>(null); | |
| const [usage, setUsage] = useState<UsageState>({ | |
| tokensPerDay: 1000000, | |
| ioRatio: 0.3, | |
| electricityRate: 0.18, | |
| pue: 1.25, | |
| analysisMonths: 24, | |
| }); | |
| // --- Hardware Registry --- | |
| const [localConfigs, setLocalConfigs] = useState<HardwareConfig[]>([ | |
| { | |
| id: 'L1', | |
| name: 'NVIDIA RTX 5090 (32GB)', | |
| hardwareCost: 2600, | |
| basePowerWatts: 150, | |
| loadPowerWatts: 500, | |
| hoursPerDay: 12, | |
| maintenanceCostMonthly: 20, | |
| quantization: 'FP16', | |
| vram: 32, | |
| context: '128k', | |
| quantity: 1, | |
| fabric: 'PCIe 5.0', | |
| harness: 'vLLM', | |
| thinkingOverhead: 0.1, | |
| enabled: true, | |
| type: 'local', | |
| isCustom: false, | |
| pros: ['Lowest latency', 'Full data privacy'], | |
| cons: ['High heat output'] | |
| }, | |
| { | |
| id: 'L4', | |
| name: 'Quad RTX 5090 NVLink', | |
| hardwareCost: 11200, | |
| basePowerWatts: 450, | |
| loadPowerWatts: 1800, | |
| hoursPerDay: 12, | |
| maintenanceCostMonthly: 50, | |
| quantization: 'FP16', | |
| vram: 32, | |
| context: '512k', | |
| quantity: 4, | |
| fabric: 'NVLink 5.0', | |
| harness: 'TensorRT-LLM', | |
| thinkingOverhead: 0.15, | |
| enabled: true, | |
| type: 'local', | |
| isCustom: false, | |
| pros: ['Exceptional TFLOPS', 'Low-latency interconnect'], | |
| cons: ['High power draw'] | |
| }, | |
| { | |
| id: 'L2', | |
| name: 'Mac Studio M4 Ultra (192GB)', | |
| hardwareCost: 5200, | |
| basePowerWatts: 45, | |
| loadPowerWatts: 210, | |
| hoursPerDay: 12, | |
| maintenanceCostMonthly: 5, | |
| quantization: 'Q8_0', | |
| vram: 192, | |
| context: '1.2M', | |
| quantity: 1, | |
| fabric: 'Unified Memory', | |
| harness: 'MLX', | |
| thinkingOverhead: 0.05, | |
| enabled: true, | |
| type: 'local', | |
| isCustom: false, | |
| pros: ['Extreme efficiency', 'Massive VRAM'], | |
| cons: ['High upfront Capex'] | |
| }, | |
| { | |
| id: 'L3', | |
| name: 'Tiiny.ai Inference Array', | |
| hardwareCost: 14500, | |
| basePowerWatts: 120, | |
| loadPowerWatts: 450, | |
| hoursPerDay: 24, | |
| maintenanceCostMonthly: 30, | |
| quantization: 'Q4_K_M', | |
| vram: 640, | |
| context: '2.5M', | |
| quantity: 1, | |
| fabric: 'High-Speed Interconnect', | |
| harness: 'Custom Pod', | |
| thinkingOverhead: 0.1, | |
| enabled: false, | |
| type: 'local', | |
| isCustom: false, | |
| pros: ['Massive context window', 'Very low power-per-GB'], | |
| cons: ['Lower peak compute'] | |
| }, | |
| { | |
| id: 'L5', | |
| name: 'NVIDIA H100 (80GB)', | |
| hardwareCost: 32000, | |
| basePowerWatts: 300, | |
| loadPowerWatts: 700, | |
| hoursPerDay: 24, | |
| maintenanceCostMonthly: 100, | |
| quantization: 'FP8', | |
| vram: 80, | |
| context: '256k', | |
| quantity: 1, | |
| fabric: 'SXM5 Interconnect', | |
| harness: 'TensorRT-LLM', | |
| enabled: false, | |
| type: 'local', | |
| isCustom: false, | |
| pros: ['Enterprise HBM3 Bandwidth', 'FP8 Acceleration'], | |
| cons: ['High entry cost', 'DC cooling required'] | |
| } | |
| ]); | |
| const [cloudProviders, setCloudProviders] = useState<CloudProvider[]>([ | |
| { id: 'gpt5_4_pro', name: 'GPT-5.4 Pro', inputPrice: 15.0, outputPrice: 60.0, thinkingMultiplier: 3.5, tier: 'Reasoning', context: '2.5M', type: 'cloud', isCustom: false, utility: { coding: 0.99, reasoning: 1.0, creative: 0.90, extraction: 1.0 }, pros: ['SOTA reasoning'], cons: ['High hidden thinking costs'] }, | |
| { id: 'claude_4_5_opus', name: 'Claude 4.5 Opus', inputPrice: 15.0, outputPrice: 75.0, thinkingMultiplier: 2.8, tier: 'Reasoning', context: '2.0M', type: 'cloud', isCustom: false, utility: { coding: 0.97, reasoning: 0.98, creative: 1.0, extraction: 0.99 }, pros: ['Human-level nuance'], cons: ['High latency'] }, | |
| { id: 'gemini_3_1_pro', name: 'Gemini 3.1 Pro', inputPrice: 1.25, outputPrice: 5.0, thinkingMultiplier: 1.2, tier: 'Flagship', context: '5.0M', type: 'cloud', isCustom: false, utility: { coding: 0.94, reasoning: 0.96, creative: 0.95, extraction: 0.99 }, pros: ['Lowest thinking overhead'], cons: ['Verbose'] }, | |
| { id: 'claude_4_5_sonnet', name: 'Claude 4.5 Sonnet', inputPrice: 3.0, outputPrice: 15.0, thinkingMultiplier: 0.5, tier: 'Flagship', context: '1.0M', type: 'cloud', isCustom: false, utility: { coding: 0.95, reasoning: 0.92, creative: 0.93, extraction: 0.98 }, pros: ['Fast and balanced'], cons: ['Context limit'] }, | |
| { id: 'sub_gemini_adv', name: 'Gemini Advanced', flatMonthly: 20, isSubscription: true, quota: 1000000, tier: 'Consumer', context: '2.0M', type: 'cloud', isCustom: false, utility: { coding: 0.90, reasoning: 0.92, creative: 0.90, extraction: 0.95 }, pros: ['Predictable cost'], cons: ['Aggressive rate limits'], thinkingMultiplier: 0 }, | |
| { id: 'sub_claude_pro', name: 'Claude Pro', flatMonthly: 20, isSubscription: true, quota: 800000, tier: 'Consumer', context: '200k', type: 'cloud', isCustom: false, utility: { coding: 0.92, reasoning: 0.90, creative: 0.95, extraction: 0.92 }, pros: ['Best UI experience'], cons: ['Strict daily caps'], thinkingMultiplier: 0 }, | |
| ]); | |
| const [selectedCloudIds, setSelectedCloudIds] = useState<string[]>(['gemini_3_1_pro', 'sub_gemini_adv']); | |
| // Handle Application Context Selection | |
| const selectApp = (id: string) => { | |
| setActiveApp(id); | |
| const app = APPS[id]; | |
| setUsage(prev => ({ | |
| ...prev, | |
| tokensPerDay: app.tokens, | |
| ioRatio: app.ratio | |
| })); | |
| if (id === 'claudeCode') setActiveTask('coding'); | |
| else if (id === 'antiGravity' || id === 'sonicAgent') setActiveTask('reasoning'); | |
| else if (id === 'nanoBanana' || id === 'motionSora') setActiveTask('creative'); | |
| }; | |
| // --- Calculations --- | |
| const results = useMemo(() => { | |
| const tokensMonthly = usage.tokensPerDay * 30; | |
| const inputTokens = tokensMonthly * usage.ioRatio; | |
| const outputTokens = tokensMonthly * (1 - usage.ioRatio); | |
| const cloudCalcs: CalculatedCloud[] = cloudProviders.map(p => { | |
| let monthly = 0; | |
| let monthlyThinkingCost = 0; | |
| let isOverQuota = false; | |
| let thinkingTokens = 0; | |
| const inputP = p.inputPrice || 0; | |
| const outputP = p.outputPrice || 0; | |
| const thinkingMult = p.thinkingMultiplier || 0; | |
| if (p.isSubscription) { | |
| monthly = p.flatMonthly || 0; | |
| isOverQuota = usage.tokensPerDay > (p.quota || 0); | |
| } else { | |
| thinkingTokens = outputTokens * thinkingMult * TASKS[activeTask].reasoningIntensity; | |
| const monthlyInputCost = (inputTokens / 1000000 * inputP); | |
| const monthlyOutputCost = (outputTokens / 1000000 * outputP); | |
| monthlyThinkingCost = (thinkingTokens / 1000000 * outputP); | |
| monthly = monthlyInputCost + monthlyOutputCost + monthlyThinkingCost; | |
| } | |
| const utility = p.utility[activeTask] || 0.8; | |
| const isCertified = APPS[activeApp].certified?.includes(p.id) ?? false; | |
| let finalUtility = isCertified ? utility * 1.05 : utility; | |
| // Subscriptions lose utility if usage exceeds quota | |
| if (isOverQuota) finalUtility *= 0.5; | |
| return { | |
| ...p, | |
| monthlyInputCost: p.isSubscription ? 0 : (inputTokens / 1000000 * inputP), | |
| monthlyOutputCost: p.isSubscription ? 0 : (outputTokens / 1000000 * outputP), | |
| monthlyThinkingCost, | |
| thinkingTokens, | |
| isOverQuota, | |
| monthlyCost: monthly, | |
| totalCost: monthly * usage.analysisMonths, | |
| utility: finalUtility, | |
| isCertified, | |
| valueScore: (finalUtility * 10000) / (monthly || 1), | |
| isSelected: selectedCloudIds.includes(p.id) | |
| }; | |
| }); | |
| const localCalcs: CalculatedLocal[] = localConfigs.map(config => { | |
| const qty = config.quantity || 1; | |
| const adjustedHours = Math.min(24, config.hoursPerDay * (1 + (config.thinkingOverhead || 0) * TASKS[activeTask].reasoningIntensity)); | |
| const scaledHardwareCost = config.hardwareCost * qty; | |
| const scaledLoadWatts = config.loadPowerWatts * qty; | |
| const scaledBaseWatts = config.basePowerWatts * qty; | |
| const dailyLoadKwh = (scaledLoadWatts * adjustedHours) / 1000; | |
| const dailyIdleKwh = (scaledBaseWatts * (24 - adjustedHours)) / 1000; | |
| const monthlyEnergyCost = (dailyLoadKwh + dailyIdleKwh) * 30 * usage.electricityRate * usage.pue; | |
| const monthlyOpex = monthlyEnergyCost + (config.maintenanceCostMonthly * qty); | |
| const totalCost = scaledHardwareCost + (monthlyOpex * usage.analysisMonths); | |
| let baseUtility = 0.85; | |
| if (config.utility) { | |
| if (typeof config.utility === 'number') { | |
| baseUtility = config.utility; | |
| } else { | |
| baseUtility = config.utility[activeTask] || 0.85; | |
| } | |
| } else { | |
| baseUtility = TASKS[activeTask].reasoningIntensity > 2 ? 0.85 : 0.94; | |
| } | |
| if (qty > 1 && config.fabric === 'Standard PCIe') baseUtility *= 0.85; | |
| if (config.fabric === 'NVLink 5.0') baseUtility *= 1.05; | |
| if (config.harness === 'TensorRT-LLM') baseUtility *= 1.02; | |
| const totalVram = config.vram * qty; | |
| const vramConflict = totalVram < 40; | |
| const finalUtility = vramConflict ? baseUtility * 0.4 : baseUtility * (config.quantization === 'FP16' ? 1.0 : 0.88); | |
| return { | |
| ...config, | |
| totalVram, | |
| monthlyEnergy: monthlyEnergyCost, | |
| monthlyOpex, | |
| totalCost, | |
| amortizedMonthly: totalCost / usage.analysisMonths, | |
| utility: finalUtility, | |
| vramConflict, | |
| valueScore: (finalUtility * 10000) / (totalCost / usage.analysisMonths), | |
| isSelected: config.enabled, | |
| scaledHardwareCost, | |
| thinkingIntensity: TASKS[activeTask].reasoningIntensity | |
| }; | |
| }); | |
| return { cloudCalcs, localCalcs, inputTokens, outputTokens }; | |
| }, [usage, localConfigs, activeTask, activeApp, selectedCloudIds, cloudProviders]); | |
| const chartData = useMemo(() => { | |
| const points: Array<Record<string, number>> = []; | |
| for (let i = 0; i <= usage.analysisMonths; i++) { | |
| const point: Record<string, number> = { month: i }; | |
| results.cloudCalcs.forEach(c => { if (c.isSelected) point[`cloud_${c.id}`] = c.monthlyCost * i; }); | |
| results.localCalcs.forEach(l => { if (l.isSelected) point[`local_${l.id}`] = l.scaledHardwareCost + (l.monthlyOpex * i); }); | |
| points.push(point); | |
| } | |
| return points; | |
| }, [results, usage.analysisMonths]); | |
| const maxVal = useMemo(() => { | |
| if (chartData.length === 0) return 1; | |
| return Math.max(...chartData.map(p => { | |
| const vals = Object.values(p).filter((v): v is number => typeof v === 'number' && v !== p.month); | |
| return vals.length > 0 ? Math.max(...vals) : 1; | |
| })) * 1.1; | |
| }, [chartData]); | |
| const toggleLocal = (id: string) => { | |
| setLocalConfigs(prev => prev.map(config => config.id === id ? { ...config, enabled: !config.enabled } : config)); | |
| }; | |
| const addNewItem = (type: 'cloud' | 'local', data: Record<string, string>) => { | |
| if (type === 'cloud') { | |
| const isSub = data.isSub === 'true'; | |
| const newItem: CloudProvider = { | |
| id: `custom_cloud_${Date.now()}`, | |
| name: data.name, | |
| inputPrice: Number(data.in), | |
| outputPrice: Number(data.out), | |
| context: data.context, | |
| tier: data.tier || 'Custom', | |
| type: 'cloud', | |
| isCustom: true, | |
| isSubscription: isSub, | |
| flatMonthly: isSub ? Number(data.flat) : 0, | |
| quota: isSub ? Number(data.quota) : undefined, | |
| thinkingMultiplier: isSub ? 0 : (Number(data.thinking) || 1.0), | |
| utility: { coding: Number(data.util)/100, reasoning: Number(data.util)/100, creative: (Number(data.util)/100) * 0.9, extraction: Math.min(1.0, (Number(data.util)/100) * 1.1) }, | |
| pros: ['Custom config'], | |
| cons: ['Unverified'] | |
| }; | |
| setCloudProviders(prev => [...prev, newItem]); | |
| setSelectedCloudIds(prev => [...prev, newItem.id]); | |
| } else { | |
| const newItem: HardwareConfig = { | |
| id: `custom_local_${Date.now()}`, | |
| name: data.name, | |
| hardwareCost: Number(data.cost), | |
| loadPowerWatts: Number(data.watts), | |
| basePowerWatts: Number(data.watts) * 0.3, | |
| vram: Number(data.vram), | |
| context: data.context, | |
| quantity: Number(data.qty), | |
| fabric: data.fabric, | |
| harness: data.harness, | |
| maintenanceCostMonthly: 15, | |
| hoursPerDay: 12, | |
| type: 'local', | |
| isCustom: true, | |
| enabled: true, | |
| quantization: 'FP16', | |
| thinkingOverhead: 0.1, | |
| pros: ['User defined'], | |
| cons: ['Custom scaling'], | |
| utility: Number(data.util)/100 | |
| }; | |
| setLocalConfigs(prev => [...prev, newItem]); | |
| } | |
| setShowAddModal(null); | |
| }; | |
| return ( | |
| <div className="min-h-screen bg-slate-50 text-slate-900 font-sans p-4 md:p-8 text-[13px]"> | |
| <header className="max-w-7xl mx-auto mb-8 space-y-6"> | |
| <div className="flex flex-col lg:flex-row lg:items-start justify-between gap-6"> | |
| <div className="lg:pt-2"> | |
| <h1 className="text-3xl font-black tracking-tight flex items-center gap-2 text-slate-900"> | |
| <Activity className="text-blue-600 w-8 h-8" /> | |
| LLM TCO Analyzer <span className="text-sm font-bold bg-blue-100 text-blue-700 px-2 py-0.5 rounded ml-2 uppercase tracking-widest">v3.6 Multi-Tier</span> | |
| </h1> | |
| <p className="text-slate-500 font-medium italic">Comparing Subscriptions, API, and Local Fabric.</p> | |
| </div> | |
| <div className="space-y-3 lg:w-[55%]"> | |
| <div className="flex items-center gap-2 bg-white p-2 rounded-2xl border shadow-sm w-full"> | |
| <span className="text-[9px] font-black text-slate-400 px-2 uppercase tracking-tighter shrink-0 border-r border-slate-100 pr-3">Task Intent:</span> | |
| <div className="flex flex-wrap gap-1.5"> | |
| {Object.entries(TASKS).map(([id, t]) => ( | |
| <div key={id} className="relative group"> | |
| <button onClick={() => setActiveTask(id)} className={`px-3 py-1.5 text-[10px] font-bold rounded-xl transition-all ${activeTask === id ? 'bg-slate-900 text-white shadow-sm' : 'bg-slate-50 text-slate-500 hover:bg-slate-100'}`}>{t.label}</button> | |
| <div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 hidden group-hover:block w-56 p-3 bg-slate-900 text-white text-[10px] rounded-xl shadow-2xl z-[150] border border-white/10 pointer-events-none transition-none"> | |
| <div className="font-black text-amber-400 mb-1 uppercase tracking-widest">{t.label} Reasoning</div> | |
| <p className="opacity-80 leading-relaxed font-medium normal-case">Thinking intensity: {t.reasoningIntensity}x</p> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-2 bg-white p-2 rounded-2xl border shadow-sm w-full"> | |
| <span className="text-[9px] font-black text-blue-600 px-2 uppercase tracking-tighter shrink-0 border-r border-blue-50 pr-3">App Ecosystem:</span> | |
| <div className="flex flex-wrap gap-1.5"> | |
| {Object.entries(APPS).map(([id, app]) => { | |
| const AppIcon = app.icon as React.ElementType; | |
| return ( | |
| <div key={id} className="relative group"> | |
| <button onClick={() => selectApp(id)} className={`px-3 py-1.5 text-[10px] font-bold rounded-xl transition-all flex items-center gap-2 ${activeApp === id ? 'bg-blue-600 text-white shadow-md border-blue-400' : 'bg-blue-50 text-blue-700 hover:bg-blue-100'}`}> | |
| <AppIcon className="w-3 h-3" /> {app.label} | |
| </button> | |
| <div className="absolute top-full left-1/2 -translate-x-1/2 mt-3 hidden group-hover:block w-56 p-3 bg-slate-900 text-white text-[10px] rounded-xl shadow-2xl z-[150] border border-white/10 pointer-events-none transition-none"> | |
| <div className="font-black text-blue-400 mb-1 uppercase tracking-widest">{app.label} Profile</div> | |
| <p className="opacity-80 leading-relaxed font-medium normal-case">{app.description}</p> | |
| <div className="absolute -top-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-slate-900 rotate-45 border-l border-t border-white/10"></div> | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <main className="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-12 gap-8"> | |
| {/* SIDEBAR */} | |
| <aside className="lg:col-span-3 space-y-6"> | |
| <section className="bg-white p-6 rounded-3xl border border-slate-200 shadow-sm"> | |
| <h3 className="text-xs font-black uppercase tracking-widest text-slate-400 mb-6 flex items-center gap-2"><Settings className="w-4 h-4" /> Usage Matrix</h3> | |
| <div className="space-y-6"> | |
| <div> | |
| <label className="block text-[10px] font-bold uppercase text-slate-500 mb-2">Analysis Window</label> | |
| <div className="grid grid-cols-2 gap-2"> | |
| {[12, 24, 36, 48].map(m => ( | |
| <button key={m} onClick={() => setUsage(prev => ({...prev, analysisMonths: m}))} className={`py-2 text-[10px] font-black rounded-xl border transition-all ${usage.analysisMonths === m ? 'bg-blue-600 border-blue-600 text-white' : 'bg-white border-slate-200'}`}>{m} Mo</button> | |
| ))} | |
| </div> | |
| </div> | |
| <div> | |
| <label className="block text-[10px] font-bold uppercase text-slate-500 mb-2 flex justify-between">Daily Load <span className="text-blue-600">{(usage.tokensPerDay / 1000000).toFixed(1)}M</span></label> | |
| <input type="range" min="100000" max="25000000" step="100000" value={usage.tokensPerDay} onChange={(e) => setUsage(prev => ({...prev, tokensPerDay: Number(e.target.value)}))} className="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600 mb-2" /> | |
| </div> | |
| <div className="pt-4 border-t border-slate-100 space-y-4"> | |
| <div className="flex justify-between items-center text-xs"> | |
| <span className="font-bold text-slate-500 uppercase text-[10px]">$/kWh</span> | |
| <input type="number" step="0.01" className="w-16 text-right font-black border-none bg-slate-50 rounded px-2 py-1" value={usage.electricityRate} onChange={e => setUsage(prev => ({...prev, electricityRate: Number(e.target.value)}))} /> | |
| </div> | |
| <div className="flex justify-between items-center group relative border-none"> | |
| <div className="flex items-center gap-1 cursor-help"><span className="text-[10px] font-bold text-slate-500 uppercase border-b border-dotted border-slate-300">Cooling (PUE)</span><Info className="w-3 h-3 text-slate-400" /></div> | |
| <input type="number" step="0.01" className="w-16 text-right font-black border-none bg-slate-50 rounded px-2 py-1 text-xs" value={usage.pue} onChange={e => setUsage(prev => ({...prev, pue: Number(e.target.value)}))} /> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section className="bg-slate-900 p-6 rounded-3xl text-white shadow-xl"> | |
| <h4 className="text-[10px] font-black uppercase tracking-widest text-amber-400 mb-4 flex items-center gap-2"><Trophy className="w-4 h-4" /> Eco Ranking</h4> | |
| <div className="space-y-3"> | |
| {[...results.cloudCalcs, ...results.localCalcs].filter(item => item.isSelected).sort((a, b) => b.valueScore - a.valueScore).map((item, i) => ( | |
| <div key={item.id} className={`bg-white/5 p-3 rounded-2xl border ${item.isCustom ? 'border-dashed border-white/20' : 'border-white/10'}`}> | |
| <div className="flex justify-between items-center"> | |
| <div className="flex items-center gap-1.5 truncate pr-2"> | |
| {item.isCustom ? <User className="w-2.5 h-2.5 text-blue-400" /> : <ShieldCheck className="w-2.5 h-2.5 text-emerald-400" />} | |
| <span className="text-xs font-bold truncate tracking-tight">{item.name}</span> | |
| </div> | |
| <span className="text-[9px] font-black bg-amber-400 text-slate-900 px-1 rounded shrink-0">#{i+1}</span> | |
| </div> | |
| <div className="flex justify-between mt-2 text-[10px] font-bold opacity-60"> | |
| <span>Utility: {(item.utility * 100).toFixed(0)}%</span> | |
| <span className="text-amber-400">Eff: {item.valueScore.toFixed(1)}</span> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </section> | |
| </aside> | |
| <div className="lg:col-span-9 space-y-6"> | |
| <div className="grid grid-cols-1 xl:grid-cols-2 gap-6"> | |
| <div className="bg-white rounded-[2rem] border border-slate-200 shadow-sm flex flex-col overflow-hidden"> | |
| <div className="p-5 bg-slate-50/50 border-b border-slate-100 flex items-center justify-between"> | |
| <h3 className="font-black text-[10px] uppercase tracking-widest text-slate-400 flex items-center gap-2"><Cloud className="w-4 h-4" /> Cloud & Subscriptions</h3> | |
| <button onClick={() => setShowAddModal('cloud')} className="p-1 text-blue-600 hover:bg-blue-50 rounded-full transition-colors flex items-center gap-1.5 text-[9px] font-black uppercase"><PlusCircle className="w-4 h-4" /> Add Model</button> | |
| </div> | |
| <div className="flex-1 overflow-auto max-h-[400px]"> | |
| <table className="w-full text-left text-xs"> | |
| <thead className="bg-slate-50/80 text-slate-400 sticky top-0 backdrop-blur-sm"> | |
| <tr> | |
| <th className="px-5 py-3 font-bold">Model</th> | |
| <th className="px-5 py-3 font-bold text-center">Context</th> | |
| <th className="px-5 py-3 font-bold text-center">Thinking</th> | |
| <th className="px-5 py-3 font-bold text-right">Opex/Mo</th> | |
| </tr> | |
| </thead> | |
| <tbody className="divide-y divide-slate-100"> | |
| {results.cloudCalcs.map((p) => ( | |
| <tr key={p.id} className={`group hover:bg-blue-50/50 transition-colors cursor-pointer ${p.isOverQuota ? 'bg-red-50/30' : ''}`} onClick={() => setDetailItem(p)}> | |
| <td className="px-5 py-4"> | |
| <div className="flex items-center gap-3"> | |
| <div className={`w-3.5 h-3.5 rounded-full border transition-all ${p.isSelected ? 'bg-blue-600 border-blue-600 shadow-sm' : 'bg-white border-slate-200'}`} onClick={(e) => { e.stopPropagation(); setSelectedCloudIds(prev => prev.includes(p.id) ? prev.filter(i => i !== p.id) : [...prev, p.id]); }}></div> | |
| <div> | |
| <div className="flex items-center gap-1.5"> | |
| <span className="font-bold text-slate-800 tracking-tight">{p.name}</span> | |
| {p.isSubscription && <span className="bg-purple-100 text-purple-700 text-[8px] font-black px-1 rounded uppercase flex items-center gap-0.5"><CreditCard className="w-2 h-2" /> Sub</span>} | |
| </div> | |
| <div className="text-[9px] text-slate-400 font-bold uppercase">{p.tier}</div> | |
| </div> | |
| </div> | |
| </td> | |
| <td className="px-5 py-4 text-center font-mono font-bold text-slate-600">{p.context}</td> | |
| <td className="px-5 py-4 text-center"> | |
| {p.isSubscription ? <span className="text-[9px] font-black text-slate-400">---</span> : ( | |
| <div className="flex flex-col items-center"> | |
| <span className="text-slate-600 font-mono font-bold text-[10px]">{p.thinkingMultiplier}x</span> | |
| <div className="w-8 h-1 bg-slate-100 rounded-full overflow-hidden mt-1"> | |
| <div className="bg-amber-400 h-full" style={{ width: `${Math.min(100, (p.thinkingMultiplier || 0) * 20)}%`}}></div> | |
| </div> | |
| </div> | |
| )} | |
| </td> | |
| <td className="px-5 py-4 text-right"> | |
| <div className={`font-black ${p.isOverQuota ? 'text-red-500' : 'text-slate-900'}`}>${p.monthlyCost.toLocaleString(undefined, { maximumFractionDigits: 0 })}</div> | |
| {p.isOverQuota && <div className="text-[8px] font-bold text-red-400 uppercase tracking-tighter">Quota Exceeded</div>} | |
| </td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div className="space-y-4"> | |
| <div className="flex items-center justify-between px-2 mb-1"> | |
| <h3 className="font-black text-[10px] uppercase tracking-widest text-slate-400 flex items-center gap-2"><Server className="w-4 h-4" /> Local Fabric</h3> | |
| <button onClick={() => setShowAddModal('local')} className="flex items-center gap-1.5 text-[10px] font-black text-emerald-600 uppercase hover:bg-emerald-50 px-2 py-1 rounded-lg transition-colors"><Plus className="w-3 h-3" /> Provision</button> | |
| </div> | |
| {results.localCalcs.map((config) => ( | |
| <div key={config.id} className={`bg-white rounded-[2rem] border transition-all cursor-pointer hover:border-blue-300 ${config.isCustom ? 'border-dashed border-slate-300 shadow-inner' : 'border-slate-200 shadow-sm'} p-6 relative ${!config.isSelected ? 'opacity-60 saturate-50' : ''}`} onClick={() => setDetailItem(config)}> | |
| <div className="flex justify-between items-start"> | |
| <div className="flex gap-4"> | |
| <div className={`w-4 h-4 rounded-full mt-1 border transition-all ${config.isSelected ? 'bg-blue-600 border-blue-600 shadow-md' : 'bg-white border-slate-300'}`} onClick={(e) => { e.stopPropagation(); toggleLocal(config.id); }}></div> | |
| <div> | |
| <div className="flex items-center gap-2"> | |
| <h4 className="font-black text-slate-800 uppercase text-sm tracking-tighter">{config.name} {config.quantity > 1 && <span className="text-blue-600">(×{config.quantity})</span>}</h4> | |
| </div> | |
| <div className="flex gap-2 mt-2"> | |
| <span className="text-[9px] font-black bg-slate-900 text-white px-2 py-0.5 rounded uppercase tracking-tighter">{config.totalVram}GB VRAM</span> | |
| <span className="text-[9px] font-black bg-indigo-50 text-indigo-700 px-2 py-0.5 rounded uppercase tracking-tighter">{config.fabric}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="text-right"> | |
| <div className="text-[9px] text-slate-400 font-bold uppercase mb-1">Utility</div> | |
| <div className={`text-2xl font-black ${config.vramConflict ? 'text-red-500' : 'text-slate-900'}`}>{(config.utility * 100).toFixed(0)}%</div> | |
| </div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| <section className="bg-white p-8 rounded-[2rem] border border-slate-200 shadow-sm relative pt-12"> | |
| <div className="flex items-center justify-between mb-8"> | |
| <h3 className="font-black text-[10px] uppercase tracking-widest text-slate-400 flex items-center gap-2"><BarChart3 className="w-4 h-4" /> Cumulative Cost Projection</h3> | |
| </div> | |
| <div className="h-80 relative"> | |
| <svg className="w-full h-full overflow-visible" viewBox={`0 0 1000 200`}> | |
| {[0, 0.5, 1].map(p => ( <line key={p} x1="0" y1={200 - (p * 200)} x2="1000" y2={200 - (p * 200)} stroke="#f1f5f9" strokeWidth="1" /> ))} | |
| {Array.from({ length: Math.floor(usage.analysisMonths / 3) + 1 }).map((_, i) => { | |
| const m = i * 3; const x = (m / usage.analysisMonths) * 1000; const isYear = m > 0 && m % 12 === 0; | |
| return ( | |
| <g key={m}> | |
| <line x1={x} y1="0" x2={x} y2="200" stroke={isYear ? "#e2e8f0" : "#f1f5f9"} strokeWidth={isYear ? "1.5" : "1"} /> | |
| <text x={x} y="215" textAnchor="middle" fontSize="9" fontWeight={isYear ? "900" : "600"} fill={isYear ? "#475569" : "#94a3b8"}>{m}M</text> | |
| </g> | |
| ); | |
| })} | |
| {results.cloudCalcs.filter(c => c.isSelected).map((c, i) => { | |
| const color = c.isSubscription ? '#a855f7' : `hsl(${210 + (i * 30)}, 70%, 50%)`; | |
| const yEnd = 200 - (c.totalCost / maxVal) * 200; | |
| return ( | |
| <g key={c.id}> | |
| <path d={`M 0 200 ${chartData.map(d => `L ${(d.month / usage.analysisMonths) * 1000} ${200 - ((d[`cloud_${c.id}`] as number) / maxVal) * 200}`).join(' ')}`} fill="none" stroke={color} strokeWidth={c.isSubscription ? "1.5" : "2"} strokeDasharray={c.isSubscription ? "0" : "5,3"} opacity={c.isSubscription ? 1 : 0.4} /> | |
| <text x="1005" y={yEnd} fill={color} fontSize="8" fontWeight="700" className="uppercase tracking-tight" dominantBaseline="middle">{c.name}</text> | |
| </g> | |
| ); | |
| })} | |
| {results.localCalcs.filter(l => l.isSelected).map((l, i) => { | |
| const color = l.id === 'L1' ? '#3b82f6' : (l.id === 'L2' ? '#10b981' : (l.id === 'L3' ? '#8b5cf6' : '#6366f1')); | |
| const yEnd = 200 - (l.totalCost / maxVal) * 200; | |
| return ( | |
| <g key={l.id}> | |
| <path d={`M 0 ${200 - (l.scaledHardwareCost / maxVal) * 200} ${chartData.map(d => `L ${(d.month / usage.analysisMonths) * 1000} ${200 - ((d[`local_${l.id}`] as number) / maxVal) * 200}`).join(' ')}`} fill="none" stroke={color} strokeWidth="5" /> | |
| <text x="1005" y={yEnd} fill={color} fontSize="10" fontWeight="900" className="uppercase tracking-tighter" dominantBaseline="middle">{l.name}</text> | |
| </g> | |
| ); | |
| })} | |
| </svg> | |
| </div> | |
| </section> | |
| </div> | |
| </main> | |
| {/* --- ADD MODAL --- */} | |
| {showAddModal && ( | |
| <div className="fixed inset-0 bg-slate-950/80 backdrop-blur-md flex items-center justify-center p-4 z-[300]"> | |
| <div className="bg-white rounded-[2.5rem] w-full max-w-lg shadow-2xl overflow-hidden border border-white/20 p-10 relative"> | |
| <button onClick={() => setShowAddModal(null)} className="absolute top-6 right-6 p-2 bg-slate-100 rounded-full text-slate-500 hover:bg-slate-200 transition-all z-10"><X className="w-4 h-4" /></button> | |
| <h2 className="text-2xl font-black mb-6 uppercase tracking-tight flex items-center gap-2"> | |
| {showAddModal === 'cloud' ? <><Cloud className="text-blue-600" /> New Cloud Item</> : <><Server className="text-emerald-600" /> New Local Item</>} | |
| </h2> | |
| <form className="space-y-4" onSubmit={(e: React.FormEvent<HTMLFormElement>) => { | |
| e.preventDefault(); | |
| const fd = new FormData(e.currentTarget); | |
| const data = Object.fromEntries(fd.entries()) as Record<string, string>; | |
| if (showAddModal === 'cloud') { | |
| addNewItem('cloud', data); | |
| } else { | |
| addNewItem('local', data); | |
| } | |
| }}> | |
| <div><label className="block text-[10px] font-black uppercase text-slate-400 mb-1">Display Name</label><input name="name" required className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 text-sm font-bold outline-none focus:ring-2 focus:ring-blue-500" /></div> | |
| {showAddModal === 'cloud' && ( | |
| <> | |
| <div className="flex gap-4 p-3 bg-slate-50 rounded-xl border border-slate-100"> | |
| <label className="flex items-center gap-2 text-[10px] font-bold uppercase"><input type="radio" name="isSub" value="true" defaultChecked /> Flat Subscription</label> | |
| <label className="flex items-center gap-2 text-[10px] font-bold uppercase"><input type="radio" name="isSub" value="false" /> Pay-As-You-Go</label> | |
| </div> | |
| <div className="grid grid-cols-2 gap-4"> | |
| <div><label className="block text-[10px] font-black uppercase text-slate-400 mb-1">Monthly Cost ($)</label><input name="flat" type="number" defaultValue="20" className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 text-sm font-bold" /></div> | |
| <div><label className="block text-[10px] font-black uppercase text-slate-400 mb-1">Max Daily Tokens</label><input name="quota" type="number" defaultValue="1000000" className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 text-sm font-bold" /></div> | |
| </div> | |
| </> | |
| )} | |
| <div className="grid grid-cols-2 gap-4"> | |
| <div><label className="block text-[10px] font-black uppercase text-slate-400 mb-1">Context Window</label><input name="context" defaultValue="200k" className="w-full bg-slate-50 border border-slate-200 rounded-xl px-4 py-3 text-sm font-bold" /></div> | |
| <div><label className="block text-[10px] font-black uppercase text-slate-400 mb-1">Intelligence (0-100)</label><input name="util" type="range" min="10" max="100" defaultValue="85" className="w-full h-2 mt-4 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600" /></div> | |
| </div> | |
| <button type="submit" className="w-full py-4 bg-slate-900 text-white rounded-2xl font-black uppercase text-[11px] tracking-widest mt-4 hover:bg-slate-800 transition-all">Generate Item</button> | |
| </form> | |
| </div> | |
| </div> | |
| )} | |
| {/* --- DRILL DOWN MODAL --- */} | |
| {detailItem && ( | |
| <div className="fixed inset-0 bg-slate-950/70 backdrop-blur-md flex items-center justify-center p-4 z-[200]"> | |
| <div className="bg-white rounded-[2.5rem] w-full max-w-2xl shadow-2xl overflow-hidden border border-white/20 relative animate-in fade-in zoom-in duration-200"> | |
| <button onClick={() => setDetailItem(null)} className="absolute top-6 right-6 p-2 bg-slate-100 rounded-full text-slate-500 hover:bg-slate-200 transition-all z-10"><X className="w-4 h-4" /></button> | |
| <div className="p-8 md:p-12"> | |
| <div className="flex flex-col md:flex-row gap-8 items-start mb-10"> | |
| <div className={`p-5 rounded-2xl ${detailItem.type === 'cloud' ? 'bg-blue-600' : 'bg-emerald-600'} text-white shadow-lg`}>{detailItem.type === 'cloud' ? <Cloud /> : <Network />}</div> | |
| <div className="flex-1"> | |
| <h2 className="text-3xl font-black text-slate-900 tracking-tighter">{detailItem.name}</h2> | |
| <div className="flex gap-2 mt-2"> | |
| <span className="text-[10px] font-black bg-slate-100 px-2 py-1 rounded uppercase tracking-widest"> | |
| {detailItem.type === 'cloud' ? (detailItem as CalculatedCloud).tier : `${(detailItem as CalculatedLocal).quantity}x Cluster`} | |
| </span> | |
| <span className="text-[10px] font-black bg-amber-100 text-amber-700 px-2 py-1 rounded uppercase flex items-center gap-1"><BrainCircuit className="w-3 h-3" /> CoT Ready</span> | |
| </div> | |
| </div> | |
| <div className="text-right"><div className="text-[10px] font-black uppercase text-slate-400 mb-1">Total Utility</div><div className="text-4xl font-black text-slate-900">{(detailItem.utility * 100).toFixed(0)}%</div></div> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-10 mb-10 text-sm"> | |
| <div className="space-y-6"> | |
| <h4 className="text-[11px] font-black uppercase tracking-widest text-slate-400 border-b border-slate-100 pb-2">Reasoning Opex Breakdown</h4> | |
| <div className="space-y-4"> | |
| {detailItem.type === 'cloud' ? (() => { | |
| const cloudItem = detailItem as CalculatedCloud; | |
| return ( | |
| <> | |
| <div className="flex justify-between items-center font-medium"><span className="text-slate-500">{cloudItem.isSubscription ? 'Flat Monthly Fee' : 'Visible Input/Output'}</span><span className="text-slate-700">${(cloudItem.isSubscription ? (cloudItem.flatMonthly || 0) : cloudItem.monthlyInputCost + cloudItem.monthlyOutputCost).toFixed(2)}</span></div> | |
| {!cloudItem.isSubscription && ( | |
| <div className="flex justify-between items-center font-bold text-amber-600"> | |
| <span className="flex items-center gap-1"><BrainCircuit className="w-3.5 h-3.5" /> Hidden Thinking</span> | |
| <span>${cloudItem.monthlyThinkingCost?.toFixed(2) || '0.00'}</span> | |
| </div> | |
| )} | |
| {cloudItem.isSubscription && cloudItem.isOverQuota && ( | |
| <div className="p-3 bg-red-50 border border-red-100 rounded-xl text-red-600 text-[10px] font-bold uppercase leading-tight"> | |
| Warning: Current load ({(usage.tokensPerDay/1000000).toFixed(1)}M/day) exceeds this plan's soft limit of ({((cloudItem.quota || 0)/1000000).toFixed(1)}M/day). Performance may be throttled. | |
| </div> | |
| )} | |
| </> | |
| ); | |
| })() : (() => { | |
| const localItem = detailItem as CalculatedLocal; | |
| return ( | |
| <> | |
| <div className="flex justify-between items-center font-medium"><span className="text-slate-500">Base Energy Cost</span><span className="text-slate-700">${localItem.monthlyEnergy.toFixed(2)}</span></div> | |
| <div className="flex justify-between items-center font-bold text-amber-600"> | |
| <span className="flex items-center gap-1"><BrainCircuit className="w-3.5 h-3.5" /> Compute Load Tax</span> | |
| <span>{(localItem.thinkingIntensity * 10).toFixed(0)}% Overhead</span> | |
| </div> | |
| </> | |
| ); | |
| })()} | |
| <div className="flex justify-between items-center pt-4 border-t border-slate-200"> | |
| <span className="text-slate-900 font-black uppercase text-xs">Total Monthly</span> | |
| <span className="text-xl font-black text-blue-600">${(detailItem.type === 'cloud' ? (detailItem as CalculatedCloud).monthlyCost : (detailItem as CalculatedLocal).monthlyOpex).toFixed(2)}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="space-y-4"> | |
| <h4 className="text-[11px] font-black uppercase tracking-widest text-slate-400 border-b border-slate-100 pb-2">Technical Profile</h4> | |
| <div className="flex justify-between text-xs font-bold text-slate-600"><span>Context Window</span><span>{detailItem.context}</span></div> | |
| <div className="flex justify-between text-xs font-bold text-slate-600"><span>Reasoning Multiplier</span><span className="bg-slate-100 px-2 rounded">{(detailItem.type === 'cloud' && (detailItem as CalculatedCloud).thinkingMultiplier) ? `${(detailItem as CalculatedCloud).thinkingMultiplier}x` : 'N/A'}</span></div> | |
| <div className="flex justify-between text-xs font-bold text-slate-600"><span>Total VRAM</span><span>{detailItem.type === 'cloud' ? 'SaaS' : `${(detailItem as CalculatedLocal).totalVram}GB`}</span></div> | |
| </div> | |
| </div> | |
| <button onClick={() => setDetailItem(null)} className="w-full py-4 bg-slate-900 text-white rounded-2xl font-black uppercase text-[11px] tracking-widest flex items-center justify-center gap-2 hover:bg-slate-800 transition-all shadow-lg">Return to Dashboard <ArrowRight className="w-4 h-4" /></button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| <footer className="max-w-7xl mx-auto mt-12 py-12 border-t text-center text-slate-400 text-[10px] font-bold uppercase tracking-widest"> | |
| Post-GPT-5 Intelligence Analyzer • April 18, 2026 | |
| </footer> | |
| </div> | |
| ); | |
| }; | |
| export default App; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React from "react"; | |
| import { createRoot } from "react-dom/client"; | |
| import App from "./App"; | |
| const root = createRoot(document.getElementById("root")); | |
| root.render(<App />); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React from "react"; | |
| import { createRoot } from "react-dom/client"; | |
| import App from "./App"; | |
| const rootElement = document.getElementById("root"); | |
| if (!rootElement) throw new Error('Failed to find the root element'); | |
| const root = createRoot(rootElement); | |
| root.render(<App />); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "name": "llm-tco-dashboard-ts", | |
| "main": "index.tsx", | |
| "dependencies": { | |
| "react": "18.2.0", | |
| "react-dom": "18.2.0", | |
| "lucide-react": "latest" | |
| }, | |
| "devDependencies": { | |
| "@types/react": "18.2.0", | |
| "@types/react-dom": "18.2.0", | |
| "typescript": "5.0.0" | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment