import { useState } from 'react'; import { Play, Rocket, Globe, ChevronDown, ChevronUp, Beaker, Shield, Zap } from 'lucide-react'; import { TutorialHint } from '@/components/game/TutorialHint'; import { ConfirmModal } from '@/components/common/ConfirmModal'; import { useGameStore } from '@/store'; import { formatNumber, formatPercent, formatDuration, VRAM_REQUIREMENTS_BY_GENERATION, DEFAULT_DATA_MIX, ALIGNMENT_METHODS, QUANTIZATION_CONFIGS, PARAMETER_OPTIONS, SIZE_TIER_MAP, SIZE_TIER_LABELS, SFT_SPECIALIZATION_BONUSES, PRETRAINING_BASE_TICKS, } from '@ai-tycoon/shared'; import type { ModelArchitecture, DataMixAllocation, SFTSpecialization, AlignmentMethod, DataDomain, QuantizationLevel, BaseModel, ModelVariant, SizeTier, ModelFamily, } from '@ai-tycoon/shared'; const DATA_MIX_PRESETS: Record = { balanced: { label: 'Balanced', mix: DEFAULT_DATA_MIX }, 'code-focused': { label: 'Code-Focused', mix: { web: 0.15, books: 0.05, code: 0.40, scientific: 0.15, conversation: 0.08, multilingual: 0.02, images: 0.03, video: 0.02, audio: 0.02, synthetic: 0.08 } }, creative: { label: 'Creative', mix: { web: 0.15, books: 0.30, code: 0.05, scientific: 0.05, conversation: 0.25, multilingual: 0.05, images: 0.05, video: 0.03, audio: 0.02, synthetic: 0.05 } }, research: { label: 'Research', mix: { web: 0.15, books: 0.10, code: 0.15, scientific: 0.35, conversation: 0.05, multilingual: 0.03, images: 0.02, video: 0.02, audio: 0.02, synthetic: 0.11 } }, }; const SFT_OPTIONS: { value: SFTSpecialization; label: string }[] = [ { value: 'general', label: 'General' }, { value: 'code', label: 'Code' }, { value: 'math', label: 'Math' }, { value: 'creative', label: 'Creative' }, { value: 'multilingual', label: 'Multilingual' }, { value: 'tool-use', label: 'Tool Use' }, ]; const DOMAIN_LABELS: Record = { web: 'Web', books: 'Books', code: 'Code', scientific: 'Scientific', conversation: 'Conversation', multilingual: 'Multilingual', images: 'Images', video: 'Video', audio: 'Audio', synthetic: 'Synthetic', }; const QUANT_LABELS: Record = { fp16: 'FP16', int8: 'INT8', int4: 'INT4', int2: 'INT2', }; export function ModelsPage() { const baseModels = useGameStore((s) => s.models.baseModels); const families = useGameStore((s) => s.models.families); const pipelines = useGameStore((s) => s.models.activeTrainingPipelines); const variantJobs = useGameStore((s) => s.models.variantJobs); const productLines = useGameStore((s) => s.models.productLines); const totalFlops = useGameStore((s) => s.compute.totalFlops); const totalVramGB = useGameStore((s) => s.compute.totalVramGB); const trainingAlloc = useGameStore((s) => s.compute.trainingAllocation); const totalData = useGameStore((s) => s.data.totalTrainingTokens); const currentEra = useGameStore((s) => s.meta.currentEra); const startTrainingPipeline = useGameStore((s) => s.startTrainingPipeline); const deployModel = useGameStore((s) => s.deployModel); const deployVariant = useGameStore((s) => s.deployVariant); const createQuantization = useGameStore((s) => s.createQuantization); const setTrainingAllocation = useGameStore((s) => s.setTrainingAllocation); const openSourceModel = useGameStore((s) => s.openSourceModel); const openSourcedModels = useGameStore((s) => s.market.openSourcedModels); const completedResearch = useGameStore((s) => s.research.completedResearch); const modelsTab = useGameStore((s) => s.modelsTab); const setModelsTab = useGameStore((s) => s.setModelsTab); const [modelName, setModelName] = useState(''); const [expandedModel, setExpandedModel] = useState(null); const [expandedPipeline, setExpandedPipeline] = useState(null); const [parameterCount, setParameterCount] = useState(7); const [contextWindow, setContextWindow] = useState(8); const [archType, setArchType] = useState<'dense' | 'moe'>('dense'); const [dataMix, setDataMix] = useState({ ...DEFAULT_DATA_MIX }); const [dataMixPreset, setDataMixPreset] = useState('balanced'); // New model lifecycle state const [familyMode, setFamilyMode] = useState<'new' | 'existing'>('new'); const [selectedFamilyId, setSelectedFamilyId] = useState(null); const [isPointRelease, setIsPointRelease] = useState(false); const [sourceModelId, setSourceModelId] = useState(null); const [sftSpecs, setSftSpecs] = useState(['general']); const [alignMethod, setAlignMethod] = useState('rlhf'); const [safetyWeight, setSafetyWeight] = useState(0.5); const trainingFlops = totalFlops * trainingAlloc; const estimatedTicks = trainingFlops > 0 ? Math.max(30, Math.ceil(PRETRAINING_BASE_TICKS / (1 + trainingFlops * 0.1))) : Infinity; const estimatedCapability = Math.min(95, Math.sqrt(trainingFlops) * 5 + Math.log10(1 + totalData / 1e8) * 10); const activePipelines = pipelines.filter(p => p.status === 'active' || p.status === 'stalled'); const activeVariantJobs = variantJobs.filter(j => j.status === 'active'); const undeployedCount = baseModels.filter(m => !m.isDeployed).length; const hasActiveJobs = activePipelines.length > 0 || activeVariantJobs.length > 0; const noModelDeployed = baseModels.length > 0 && !baseModels.some(m => m.isDeployed); const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'] as const; const currentEraIdx = eraOrder.indexOf(currentEra); const hasAlignmentResearch = completedResearch.some(r => r === 'alignment-research' || r === 'interpretability' || r === 'constitutional-ai', ); // Computed size tier const sizeTier: SizeTier = SIZE_TIER_MAP[parameterCount] ?? 'small'; // Model name preview const familyNameForPreview = familyMode === 'new' ? (modelName.trim() || `Family ${families.length + 1}`) : (families.find(f => f.id === selectedFamilyId)?.name ?? 'Family'); const nextVersion = (() => { if (!isPointRelease || !sourceModelId) return 1.0; const src = baseModels.find(m => m.id === sourceModelId); return src ? Math.round((src.version + 0.1) * 10) / 10 : 1.0; })(); const modelNamePreview = `${familyNameForPreview} ${SIZE_TIER_LABELS[sizeTier]} v${nextVersion.toFixed(1)}`; const handleStartTraining = () => { if (trainingFlops === 0) return; const architecture: ModelArchitecture = { type: archType, totalParameters: parameterCount, activeParameters: archType === 'moe' ? Math.ceil(parameterCount * 0.25) : parameterCount, contextWindow, vocabularySize: 32000, ...(archType === 'moe' ? { expertCount: 8, expertTopK: 2 } : {}), }; startTrainingPipeline({ ...(familyMode === 'new' ? { familyName: modelName.trim() || `Family ${families.length + 1}` } : { familyId: selectedFamilyId! }), architecture, dataMix, allocatedComputeFraction: 1.0, targetTokens: totalData, totalTicks: estimatedTicks, sftSpecializations: sftSpecs, alignmentMethod: alignMethod, alignmentSafetyWeight: safetyWeight, isPointRelease, sourceModelId: sourceModelId ?? undefined, }); setModelName(''); setIsPointRelease(false); setSourceModelId(null); }; const handlePresetChange = (presetKey: string) => { setDataMixPreset(presetKey); const preset = DATA_MIX_PRESETS[presetKey]; if (preset) setDataMix({ ...preset.mix }); }; const handleMixSlider = (domain: DataDomain, value: number) => { const newMix = { ...dataMix, [domain]: value / 100 }; const total = Object.values(newMix).reduce((s, v) => s + v, 0); if (total > 0) { for (const key of Object.keys(newMix) as DataDomain[]) { newMix[key] = newMix[key] / total; } } setDataMix(newMix); setDataMixPreset('custom'); }; return (

Models

Split compute between training (building new models) and inference (serving customers). Deploy trained models to start earning revenue.
{([ { id: 'overview' as const, label: 'Overview' }, { id: 'train' as const, label: 'Train New' }, { id: 'models' as const, label: `Families${families.length > 0 ? ` (${families.length})` : ''}` }, { id: 'products' as const, label: 'Products' }, ]).map(tab => ( ))}
{/* Compute Allocation — always visible */}

Compute Allocation

Training setTrainingAllocation(Number(e.target.value) / 100)} className="flex-1 accent-accent" /> Inference
{formatPercent(trainingAlloc)} {formatPercent(1 - trainingAlloc)}
{/* Active Training Pipelines */} {modelsTab === 'overview' && activePipelines.length > 0 && (

Active Training

{activePipelines.map(pipeline => { const stage = pipeline.currentStage === 'pretraining' ? pipeline.stages.pretraining : pipeline.currentStage === 'sft' ? pipeline.stages.sft : pipeline.stages.alignment; if (!stage) return null; const progress = stage.progressTicks / stage.totalTicks; const generation = families.find(f => f.id === pipeline.familyId)?.generation ?? 1; const reqVram = VRAM_REQUIREMENTS_BY_GENERATION[generation] ?? 0; const isStalled = pipeline.status === 'stalled'; const isExpanded = expandedPipeline === pipeline.id; const stageLabel = pipeline.currentStage === 'pretraining' ? 'Pre-training' : pipeline.currentStage === 'sft' ? 'SFT' : 'Alignment'; const recentEvents = pipeline.events.slice(-3).reverse(); return (
{pipeline.modelName} {pipeline.architecture.totalParameters}B {pipeline.architecture.type.toUpperCase()} · {pipeline.architecture.contextWindow}K ctx
{stageLabel} {isStalled ? Stalled : `${formatPercent(progress)}`}
{isStalled ? `Requires ${formatNumber(reqVram)} GB VRAM (have ${formatNumber(totalVramGB)} GB)` : `ETA: ${formatDuration(stage.totalTicks - stage.progressTicks)}`}
{isExpanded && (
{pipeline.currentStage === 'pretraining' && (
Loss: {pipeline.stages.pretraining.lossValue.toFixed(3)} {' · '}Chinchilla ratio: {pipeline.stages.pretraining.chinchillaRatio.toFixed(1)}
)} {recentEvents.length > 0 && (
Recent Events {recentEvents.map(event => (
{event.description}
))}
)}
)}
); })}
)} {/* Active Variant Jobs */} {modelsTab === 'overview' && activeVariantJobs.length > 0 && (

Variant Jobs

{activeVariantJobs.map(job => { const base = baseModels.find(m => m.id === job.baseModelId); const progress = job.progressTicks / job.totalTicks; return (
{('variantName' in job.config ? (job.config as { variantName: string }).variantName : base?.name) ?? 'Variant'} {job.jobType}
{formatPercent(progress)} · ETA: {formatDuration(job.totalTicks - job.progressTicks)}
); })}
)} {/* Train New Model */} {modelsTab === 'train' &&

Train New Model

{isPointRelease && sourceModelId && (
Point Release — iterating on {baseModels.find(m => m.id === sourceModelId)?.name ?? 'model'} (40% training time)
)}
{/* Family selector */}
{familyMode === 'new' ? ( setModelName(e.target.value)} placeholder={`Family ${families.length + 1}`} className="w-full bg-surface-800 border border-surface-600 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-accent/50" /> ) : ( )}
{/* Architecture & Parameters */}
{/* Parameters with size tier indicator */}
{SIZE_TIER_LABELS[sizeTier]}
{/* Data Mix */}
{Object.entries(DATA_MIX_PRESETS).map(([key, preset]) => ( ))}

Total must equal 100% — other values adjust proportionally.

{(Object.keys(DOMAIN_LABELS) as DataDomain[]).map(domain => (
{DOMAIN_LABELS[domain]} handleMixSlider(domain, Number(e.target.value))} className="flex-1 accent-accent h-1" /> {Math.round(dataMix[domain] * 100)}%
))}
{/* SFT Configuration */}
{SFT_OPTIONS.map(opt => ( ))}
{sftSpecs.length > 0 && (
Bonus preview: {sftSpecs.map(spec => { const bonuses = SFT_SPECIALIZATION_BONUSES[spec]; if (!bonuses) return null; const positives = Object.entries(bonuses).filter(([, v]) => v > 0).map(([k, v]) => `${k} +${v}`); const negatives = Object.entries(bonuses).filter(([, v]) => v < 0).map(([k, v]) => `${k} ${v}`); return ( {spec} {positives.length > 0 && {positives.join(', ')}} {negatives.length > 0 && {negatives.join(', ')}} ); })}
)}
{/* Alignment Configuration */}
{hasAlignmentResearch ? (
{(Object.keys(ALIGNMENT_METHODS) as AlignmentMethod[]).map(method => { const isAvailable = completedResearch.includes(ALIGNMENT_METHODS[method].requiredResearch); return ( ); })}
Safety setSafetyWeight(Number(e.target.value) / 100)} className="flex-1 accent-accent h-1" /> Helpful {Math.round(safetyWeight * 100)}%
) : (
Requires alignment research — defaults to RLHF
)}
{/* Stats */}
Training Compute
{formatNumber(trainingFlops)} FLOPS
Available VRAM
{formatNumber(totalVramGB)} GB
Training Data
{formatNumber(totalData)} tokens
Est. Time
{trainingFlops > 0 ? formatDuration(estimatedTicks) : 'N/A'}
Estimated capability: {estimatedCapability.toFixed(1)}/100 {archType === 'moe' && (+15% MoE bonus)}
{/* Model name preview */}
Model name: {modelNamePreview}
{/* Start button */}
{trainingFlops === 0 && totalFlops === 0 && (

Build a data center and order racks first

)} {trainingFlops === 0 && totalFlops > 0 && (

Allocate compute to training above

)}
} {/* Model Families & Trained Models */} {modelsTab === 'models' && families.length > 0 && (

Model Families

{families.map(family => { const familyModels = baseModels.filter(m => m.familyId === family.id); const variants = family.variants; const isExpanded = expandedModel === family.id; return (
{/* Family header */}

{family.name} Gen {family.generation}

{/* Model rows */} {familyModels.map(model => (
{model.name} {model.architecture.totalParameters}B Cap: {model.rawCapability.toFixed(1)}
{model.isDeployed ? ( Deployed ) : ( )}
))} {familyModels.length === 0 && (

Training in progress...

)} {/* Expanded: details, quantize, eval for each model */} {isExpanded && familyModels.length > 0 && (
{familyModels.map(model => (
{model.name}
))} {variants.length > 0 && (
Quantized Variants {variants.map(variant => ( deployVariant(family.id, variant.id)} /> ))}
)}
)}
); })}
)} {/* Product Lines */} {modelsTab === 'products' &&

Product Lines

{noModelDeployed && (

No model deployed yet. Deploy a model to start earning revenue.

)} {productLines.map(pl => (

{pl.name}

{pl.modelId ? `Running: ${baseModels.find(m => m.id === pl.modelId)?.name ?? 'Unknown'}` : 'No model deployed'}
{pl.isActive ? 'Active' : 'Inactive'}
))}
} {modelsTab === 'models' && families.length === 0 && (

No model families yet. Train your first model to get started.

)} {modelsTab === 'overview' && !hasActiveJobs && (

No active training or evaluation jobs.

{families.length > 0 && ( )}
)}
); } function ModelActions({ model, isOpenSourced, onDeploy, onOpenSource }: { model: BaseModel; isOpenSourced: boolean; onDeploy: () => void; onOpenSource: () => void; }) { const [confirmAction, setConfirmAction] = useState<'deploy' | 'opensource' | null>(null); return ( <> {!isOpenSourced && model.isDeployed && ( )} {model.isDeployed ? ( Deployed ) : ( )} {confirmAction === 'deploy' && ( { onDeploy(); setConfirmAction(null); }} onCancel={() => setConfirmAction(null)} /> )} {confirmAction === 'opensource' && ( { onOpenSource(); setConfirmAction(null); }} onCancel={() => setConfirmAction(null)} /> )} ); } function ModelDetails({ model }: { model: BaseModel }) { return (
Architecture
{model.architecture.totalParameters}B {model.architecture.type}
Context
{model.architecture.contextWindow >= 1024 ? `${model.architecture.contextWindow / 1024}M` : `${model.architecture.contextWindow}K`}
Stages
{model.trainingStagesCompleted.join(' + ')}
{(['reasoning', 'coding', 'creative', 'math', 'knowledge', 'multimodal', 'agents', 'speed', 'contextUtilization'] as const).map(cap => (
{cap === 'contextUtilization' ? 'Context Util.' : cap}
{model.capabilities[cap].toFixed(1)}
))}
Safety
{model.safetyProfile.overallSafety.toFixed(1)}
Harm Avoidance
{model.safetyProfile.harmAvoidance.toFixed(1)}
Refusal Rate
{formatPercent(model.safetyProfile.refusalRate)}
); } function QuantizationCreator({ model, completedResearch, onQuantize }: { model: BaseModel; completedResearch: string[]; onQuantize: (baseModelId: string, level: QuantizationLevel, name: string) => void; }) { const [showCreator, setShowCreator] = useState(false); const [quantLevel, setQuantLevel] = useState('int8'); const hasQuantization = completedResearch.includes('quantization') || completedResearch.includes('model-compression'); if (!hasQuantization) return null; if (!showCreator) { return ( ); } return (
Quantize {model.name}
{(Object.keys(QUANTIZATION_CONFIGS) as QuantizationLevel[]).map(level => { const cfg = QUANTIZATION_CONFIGS[level]; return ( ); })}
); } function VariantCard({ variant, familyId, onDeploy }: { variant: ModelVariant; familyId: string; onDeploy: () => void; }) { const [isExpanded, setIsExpanded] = useState(false); return (
{variant.name} Quantized {variant.quantization && {variant.quantization.toUpperCase()}}
{variant.costMultiplier < 1 ? `${(variant.costMultiplier * 100).toFixed(0)}% cost` : ''} {variant.speedMultiplier > 1 ? ` ${variant.speedMultiplier.toFixed(1)}x speed` : ''} {variant.isDeployed ? ( Deployed ) : ( )}
{isExpanded && (
{(['reasoning', 'coding', 'creative', 'math', 'knowledge', 'speed'] as const).map(cap => (
{cap}
{variant.capabilities[cap].toFixed(1)}
))}
)}
); } function StageBar({ label, active, complete, progress }: { label: string; active: boolean; complete: boolean; progress: number; }) { return (
{label}
{active && !complete && (
)}
); }