156 lines
4.7 KiB
TypeScript
156 lines
4.7 KiB
TypeScript
|
|
/**
|
||
|
|
* Intelligent Optimizer Panel - Protocol 13
|
||
|
|
* Displays real-time optimizer state: phase, strategy, progress, confidence
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { useEffect, useState } from 'react';
|
||
|
|
|
||
|
|
interface OptimizerState {
|
||
|
|
available: boolean;
|
||
|
|
current_phase?: string;
|
||
|
|
current_strategy?: string;
|
||
|
|
trial_number?: number;
|
||
|
|
total_trials?: number;
|
||
|
|
is_multi_objective?: boolean;
|
||
|
|
latest_recommendation?: {
|
||
|
|
strategy: string;
|
||
|
|
confidence: number;
|
||
|
|
reasoning: string;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export function OptimizerPanel({ studyId }: { studyId: string }) {
|
||
|
|
const [state, setState] = useState<OptimizerState | null>(null);
|
||
|
|
const [error, setError] = useState<string | null>(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const fetchState = async () => {
|
||
|
|
try {
|
||
|
|
const res = await fetch(`/api/optimization/studies/${studyId}/optimizer-state`);
|
||
|
|
if (!res.ok) {
|
||
|
|
throw new Error(`HTTP ${res.status}`);
|
||
|
|
}
|
||
|
|
const data = await res.json();
|
||
|
|
setState(data);
|
||
|
|
setError(null);
|
||
|
|
} catch (err) {
|
||
|
|
console.error('Failed to fetch optimizer state:', err);
|
||
|
|
setError('Failed to load');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
fetchState();
|
||
|
|
const interval = setInterval(fetchState, 1000); // Update every second
|
||
|
|
return () => clearInterval(interval);
|
||
|
|
}, [studyId]);
|
||
|
|
|
||
|
|
if (error) {
|
||
|
|
return (
|
||
|
|
<div className="bg-dark-700 rounded-lg p-6 border border-dark-600">
|
||
|
|
<h3 className="text-lg font-semibold mb-4 text-dark-100">Intelligent Optimizer</h3>
|
||
|
|
<div className="text-dark-400 text-sm">
|
||
|
|
{error}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!state?.available) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Format phase name for display
|
||
|
|
const formatPhase = (phase?: string) => {
|
||
|
|
if (!phase) return 'Unknown';
|
||
|
|
return phase
|
||
|
|
.split('_')
|
||
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||
|
|
.join(' ');
|
||
|
|
};
|
||
|
|
|
||
|
|
// Format strategy name for display
|
||
|
|
const formatStrategy = (strategy?: string) => {
|
||
|
|
if (!strategy) return 'Not set';
|
||
|
|
return strategy.toUpperCase();
|
||
|
|
};
|
||
|
|
|
||
|
|
const progress = state.trial_number && state.total_trials
|
||
|
|
? (state.trial_number / state.total_trials) * 100
|
||
|
|
: 0;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="bg-dark-700 rounded-lg p-6 border border-dark-600">
|
||
|
|
<h3 className="text-lg font-semibold mb-4 text-dark-100 flex items-center gap-2">
|
||
|
|
Intelligent Optimizer
|
||
|
|
{state.is_multi_objective && (
|
||
|
|
<span className="text-xs bg-purple-500/20 text-purple-300 px-2 py-1 rounded">
|
||
|
|
Multi-Objective
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</h3>
|
||
|
|
|
||
|
|
<div className="space-y-4">
|
||
|
|
{/* Phase */}
|
||
|
|
<div>
|
||
|
|
<div className="text-sm text-dark-300 mb-1">Phase</div>
|
||
|
|
<div className="text-lg font-semibold text-primary-400">
|
||
|
|
{formatPhase(state.current_phase)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Strategy */}
|
||
|
|
<div>
|
||
|
|
<div className="text-sm text-dark-300 mb-1">Current Strategy</div>
|
||
|
|
<div className="text-lg font-semibold text-blue-400">
|
||
|
|
{formatStrategy(state.current_strategy)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Progress */}
|
||
|
|
<div>
|
||
|
|
<div className="text-sm text-dark-300 mb-1">Progress</div>
|
||
|
|
<div className="text-lg text-dark-100">
|
||
|
|
{state.trial_number || 0} / {state.total_trials || 0} trials
|
||
|
|
</div>
|
||
|
|
<div className="w-full bg-dark-500 rounded-full h-2 mt-2">
|
||
|
|
<div
|
||
|
|
className="bg-primary-400 h-2 rounded-full transition-all duration-300"
|
||
|
|
style={{ width: `${progress}%` }}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Confidence (if available) */}
|
||
|
|
{state.latest_recommendation && (
|
||
|
|
<div>
|
||
|
|
<div className="text-sm text-dark-300 mb-1">Confidence</div>
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<div className="flex-1 bg-dark-500 rounded-full h-2">
|
||
|
|
<div
|
||
|
|
className="bg-green-400 h-2 rounded-full transition-all duration-300"
|
||
|
|
style={{
|
||
|
|
width: `${state.latest_recommendation.confidence * 100}%`
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<span className="text-sm font-mono text-dark-200 min-w-[3rem] text-right">
|
||
|
|
{(state.latest_recommendation.confidence * 100).toFixed(0)}%
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* Reasoning (if available) */}
|
||
|
|
{state.latest_recommendation && (
|
||
|
|
<div>
|
||
|
|
<div className="text-sm text-dark-300 mb-1">Reasoning</div>
|
||
|
|
<div className="text-sm text-dark-100 bg-dark-800 rounded p-3 border border-dark-600">
|
||
|
|
{state.latest_recommendation.reasoning}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|