Add Week 3 polish and late-game features
VC funding system (seed through IPO with requirements gating), 15 achievements with engine checker, model tuning presets and unlockable sliders, overload policy controls, open-source mechanic with reputation boost, enhanced Recharts analytics (subscriber/reputation/revenue vs expenses charts), M&A acquisition system, sidebar NEW badges on era transitions, tutorial hints, and wired-up settings toggles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { Brain, Play, Rocket } from 'lucide-react';
|
||||
import { Brain, Play, Rocket, Globe, SlidersHorizontal, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { useGameStore } from '@/store';
|
||||
import { formatNumber, formatPercent, formatDuration } from '@ai-tycoon/shared';
|
||||
import type { TuningPreset } from '@ai-tycoon/shared';
|
||||
|
||||
export function ModelsPage() {
|
||||
const trainedModels = useGameStore((s) => s.models.trainedModels);
|
||||
@@ -13,8 +14,14 @@ export function ModelsPage() {
|
||||
const startTraining = useGameStore((s) => s.startTraining);
|
||||
const deployModel = useGameStore((s) => s.deployModel);
|
||||
const setTrainingAllocation = useGameStore((s) => s.setTrainingAllocation);
|
||||
const openSourceModel = useGameStore((s) => s.openSourceModel);
|
||||
const setModelTuning = useGameStore((s) => s.setModelTuning);
|
||||
const openSourcedModels = useGameStore((s) => s.market.openSourcedModels);
|
||||
const completedResearch = useGameStore((s) => s.research.completedResearch);
|
||||
const hasTuningSliders = completedResearch.includes('alignment-research');
|
||||
|
||||
const [modelName, setModelName] = useState('');
|
||||
const [expandedModel, setExpandedModel] = useState<string | null>(null);
|
||||
|
||||
const trainingFlops = totalFlops * trainingAlloc;
|
||||
const estimatedTicks = trainingFlops > 0 ? Math.max(30, Math.ceil(120 / (1 + trainingFlops * 0.1))) : Infinity;
|
||||
@@ -122,31 +129,102 @@ export function ModelsPage() {
|
||||
{trainedModels.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<h3 className="font-semibold">Trained Models</h3>
|
||||
{trainedModels.map(model => (
|
||||
<div key={model.id} className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium">{model.name}</h4>
|
||||
<div className="text-xs text-surface-400">
|
||||
Gen {model.generation} · Benchmark: {model.benchmarkScore.toFixed(1)}/100 · Safety: {model.safetyScore.toFixed(0)}/100
|
||||
{trainedModels.map(model => {
|
||||
const isExpanded = expandedModel === model.id;
|
||||
const isOpenSourced = openSourcedModels.includes(model.id);
|
||||
|
||||
return (
|
||||
<div key={model.id} className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={() => setExpandedModel(isExpanded ? null : model.id)} className="text-surface-400 hover:text-surface-200">
|
||||
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
</button>
|
||||
<div>
|
||||
<h4 className="font-medium">{model.name}</h4>
|
||||
<div className="text-xs text-surface-400">
|
||||
Gen {model.generation} · Benchmark: {model.benchmarkScore.toFixed(1)}/100 · Safety: {model.safetyScore.toFixed(0)}/100
|
||||
{isOpenSourced && <span className="ml-2 text-blue-400">Open Source</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{!isOpenSourced && model.isDeployed && (
|
||||
<button
|
||||
onClick={() => openSourceModel(model.id)}
|
||||
className="flex items-center gap-1 bg-blue-600/20 hover:bg-blue-600/30 text-blue-400 border border-blue-600/30 rounded px-3 py-1.5 text-xs"
|
||||
>
|
||||
<Globe size={12} />
|
||||
Open Source
|
||||
</button>
|
||||
)}
|
||||
{model.isDeployed ? (
|
||||
<span className="text-xs px-2 py-1 rounded-full bg-success/20 text-success">Deployed</span>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => deployModel(model.id)}
|
||||
className="flex items-center gap-1 bg-accent hover:bg-accent-dark text-white rounded px-3 py-1.5 text-xs"
|
||||
>
|
||||
<Rocket size={12} />
|
||||
Deploy
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{model.isDeployed ? (
|
||||
<span className="text-xs px-2 py-1 rounded-full bg-success/20 text-success">Deployed</span>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => deployModel(model.id)}
|
||||
className="flex items-center gap-1 bg-accent hover:bg-accent-dark text-white rounded px-3 py-1.5 text-xs"
|
||||
>
|
||||
<Rocket size={12} />
|
||||
Deploy
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="mt-4 pt-4 border-t border-surface-700 space-y-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<SlidersHorizontal size={14} className="text-surface-400" />
|
||||
<span className="text-sm font-medium">Model Tuning</span>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-surface-400 mb-1">Preset</label>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{(['helpful-safe', 'max-capability', 'enterprise', 'creative'] as TuningPreset[]).map(preset => (
|
||||
<button
|
||||
key={preset}
|
||||
onClick={() => setModelTuning(model.id, { preset })}
|
||||
className={`px-3 py-1.5 rounded text-xs border transition-colors ${
|
||||
model.tuning.preset === preset
|
||||
? 'bg-accent/20 border-accent text-accent-light'
|
||||
: 'bg-surface-800 border-surface-600 text-surface-300 hover:border-surface-500'
|
||||
}`}
|
||||
>
|
||||
{preset === 'helpful-safe' ? 'Helpful & Safe' : preset === 'max-capability' ? 'Max Capability' : preset === 'enterprise' ? 'Enterprise' : 'Creative'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasTuningSliders && (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<TuningSlider label="Safety Level" value={model.tuning.safetyLevel ?? 0.7} onChange={(v) => setModelTuning(model.id, { safetyLevel: v })} />
|
||||
<TuningSlider label="Creativity" value={model.tuning.creativity ?? 0.5} onChange={(v) => setModelTuning(model.id, { creativity: v })} />
|
||||
<TuningSlider label="Verbosity" value={model.tuning.verbosity ?? 0.5} onChange={(v) => setModelTuning(model.id, { verbosity: v })} />
|
||||
<TuningSlider label="Speed vs Quality" value={model.tuning.speedQuality ?? 0.5} onChange={(v) => setModelTuning(model.id, { speedQuality: v })} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-3 gap-3 text-xs">
|
||||
<div className="bg-surface-800 rounded-lg p-2">
|
||||
<span className="text-surface-400">Reasoning</span>
|
||||
<div className="font-mono mt-0.5">{model.capabilities.reasoning.toFixed(1)}</div>
|
||||
</div>
|
||||
<div className="bg-surface-800 rounded-lg p-2">
|
||||
<span className="text-surface-400">Coding</span>
|
||||
<div className="font-mono mt-0.5">{model.capabilities.coding.toFixed(1)}</div>
|
||||
</div>
|
||||
<div className="bg-surface-800 rounded-lg p-2">
|
||||
<span className="text-surface-400">Creative</span>
|
||||
<div className="font-mono mt-0.5">{model.capabilities.creative.toFixed(1)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -171,3 +249,22 @@ export function ModelsPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TuningSlider({ label, value, onChange }: { label: string; value: number; onChange: (v: number) => void }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-1">
|
||||
<span className="text-surface-400">{label}</span>
|
||||
<span className="font-mono text-surface-300">{(value * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={100}
|
||||
value={value * 100}
|
||||
onChange={(e) => onChange(Number(e.target.value) / 100)}
|
||||
className="w-full accent-accent h-1.5"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user