feat: Dashboard improvements and configuration updates
Dashboard: - Enhanced terminal components (ClaudeTerminal, GlobalClaudeTerminal) - Improved MarkdownRenderer for better documentation display - Updated convergence plots (ConvergencePlot, PlotlyConvergencePlot) - Refined Home, Analysis, Dashboard, Setup, Results pages - Added StudyContext improvements - Updated vite.config for better dev experience Configuration: - Updated CLAUDE.md with latest instructions - Enhanced launch_dashboard.py - Updated config.py settings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -161,15 +161,12 @@ export const ClaudeTerminal: React.FC<ClaudeTerminalProps> = ({
|
||||
setIsConnecting(true);
|
||||
setError(null);
|
||||
|
||||
// Always use Atomizer root as working directory so Claude has access to:
|
||||
// - CLAUDE.md (system instructions)
|
||||
// - .claude/skills/ (skill definitions)
|
||||
// Let backend determine the working directory (ATOMIZER_ROOT)
|
||||
// Pass study_id as parameter so we can inform Claude about the context
|
||||
const workingDir = 'C:/Users/Antoine/Atomizer';
|
||||
const studyParam = selectedStudy?.id ? `&study_id=${selectedStudy.id}` : '';
|
||||
const studyParam = selectedStudy?.id ? `?study_id=${selectedStudy.id}` : '';
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const ws = new WebSocket(`${protocol}//${window.location.host}/api/terminal/claude?working_dir=${workingDir}${studyParam}`);
|
||||
const ws = new WebSocket(`${protocol}//${window.location.host}/api/terminal/claude${studyParam}`);
|
||||
|
||||
ws.onopen = () => {
|
||||
setIsConnected(true);
|
||||
|
||||
@@ -20,8 +20,13 @@ interface Trial {
|
||||
trial_number: number;
|
||||
values: number[];
|
||||
state?: string;
|
||||
constraint_satisfied?: boolean;
|
||||
user_attrs?: Record<string, any>;
|
||||
}
|
||||
|
||||
// Penalty threshold - objectives above this are considered failed/penalty trials
|
||||
const PENALTY_THRESHOLD = 100000;
|
||||
|
||||
interface ConvergencePlotProps {
|
||||
trials: Trial[];
|
||||
objectiveIndex?: number;
|
||||
@@ -38,9 +43,22 @@ export function ConvergencePlot({
|
||||
const convergenceData = useMemo(() => {
|
||||
if (!trials || trials.length === 0) return [];
|
||||
|
||||
// Sort by trial number
|
||||
// Sort by trial number, filtering out failed/penalty trials
|
||||
const sortedTrials = [...trials]
|
||||
.filter(t => t.values && t.values.length > objectiveIndex && t.state !== 'FAIL')
|
||||
.filter(t => {
|
||||
// Must have valid values
|
||||
if (!t.values || t.values.length <= objectiveIndex) return false;
|
||||
// Filter out failed state
|
||||
if (t.state === 'FAIL') return false;
|
||||
// Filter out penalty values (e.g., 1000000 = solver failure)
|
||||
const val = t.values[objectiveIndex];
|
||||
if (val >= PENALTY_THRESHOLD) return false;
|
||||
// Filter out constraint violations
|
||||
if (t.constraint_satisfied === false) return false;
|
||||
// Filter out pruned trials
|
||||
if (t.user_attrs?.pruned === true || t.user_attrs?.fail_reason) return false;
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => a.trial_number - b.trial_number);
|
||||
|
||||
if (sortedTrials.length === 0) return [];
|
||||
|
||||
@@ -33,12 +33,14 @@ export const GlobalClaudeTerminal: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Terminal panel
|
||||
// Terminal panel - responsive sizing
|
||||
// On mobile portrait: full width with small margins
|
||||
// On tablet/desktop: fixed size panel
|
||||
return (
|
||||
<div className={`fixed z-50 transition-all duration-200 ${
|
||||
isExpanded
|
||||
? 'inset-4'
|
||||
: 'bottom-6 right-6 w-[650px] h-[500px]'
|
||||
? 'inset-2 sm:inset-4'
|
||||
: 'bottom-2 right-2 left-2 h-[400px] sm:bottom-6 sm:right-6 sm:left-auto sm:w-[650px] sm:h-[500px]'
|
||||
}`}>
|
||||
<ClaudeTerminal
|
||||
isExpanded={isExpanded}
|
||||
|
||||
@@ -10,13 +10,34 @@ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
interface MarkdownRendererProps {
|
||||
content: string;
|
||||
className?: string;
|
||||
studyId?: string; // Optional study ID for resolving relative image paths
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared markdown renderer with syntax highlighting, GFM, and LaTeX support.
|
||||
* Used by both the Home page (README display) and Results page (reports).
|
||||
*/
|
||||
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, className = '' }) => {
|
||||
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, className = '', studyId }) => {
|
||||
// Helper to resolve image URLs - converts relative paths to API endpoints
|
||||
const resolveImageSrc = (src: string | undefined): string => {
|
||||
if (!src) return '';
|
||||
|
||||
// If it's already an absolute URL or data URL, return as-is
|
||||
if (src.startsWith('http://') || src.startsWith('https://') || src.startsWith('data:')) {
|
||||
return src;
|
||||
}
|
||||
|
||||
// If we have a studyId, route through the API
|
||||
if (studyId) {
|
||||
// Remove leading ./ or / from the path
|
||||
const cleanPath = src.replace(/^\.?\//, '');
|
||||
return `/api/optimization/studies/${studyId}/image/${cleanPath}`;
|
||||
}
|
||||
|
||||
// Fallback: return original src
|
||||
return src;
|
||||
};
|
||||
|
||||
return (
|
||||
<article className={`markdown-body max-w-none ${className}`}>
|
||||
<ReactMarkdown
|
||||
@@ -165,12 +186,17 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content, cla
|
||||
hr: () => (
|
||||
<hr className="my-8 border-dark-600" />
|
||||
),
|
||||
// Images
|
||||
// Images - resolve relative paths through API
|
||||
img: ({ src, alt }) => (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
src={resolveImageSrc(src)}
|
||||
alt={alt || ''}
|
||||
className="my-4 rounded-lg max-w-full h-auto border border-dark-600"
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
// Hide broken images
|
||||
(e.target as HTMLImageElement).style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
|
||||
@@ -5,8 +5,8 @@ export const MainLayout = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-dark-900 text-dark-50 font-sans">
|
||||
<Sidebar />
|
||||
<main className="ml-64 min-h-screen">
|
||||
<div className="max-w-7xl mx-auto p-8">
|
||||
<main className="ml-64 min-h-screen p-6">
|
||||
<div className="max-w-6xl">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -19,8 +19,12 @@ interface Trial {
|
||||
params: Record<string, number>;
|
||||
user_attrs?: Record<string, any>;
|
||||
source?: 'FEA' | 'NN' | 'V10_FEA';
|
||||
constraint_satisfied?: boolean;
|
||||
}
|
||||
|
||||
// Penalty threshold - objectives above this are considered failed/penalty trials
|
||||
const PENALTY_THRESHOLD = 100000;
|
||||
|
||||
interface PlotlyConvergencePlotProps {
|
||||
trials: Trial[];
|
||||
objectiveIndex?: number;
|
||||
@@ -58,6 +62,15 @@ export function PlotlyConvergencePlot({
|
||||
const val = t.values?.[objectiveIndex] ?? t.user_attrs?.[objectiveName] ?? null;
|
||||
if (val === null || !isFinite(val)) return;
|
||||
|
||||
// Filter out failed/penalty trials:
|
||||
// 1. Objective above penalty threshold (e.g., 1000000 = solver failure)
|
||||
// 2. constraint_satisfied explicitly false
|
||||
// 3. user_attrs indicates pruned/failed
|
||||
const isPenalty = val >= PENALTY_THRESHOLD;
|
||||
const isFailed = t.constraint_satisfied === false;
|
||||
const isPruned = t.user_attrs?.pruned === true || t.user_attrs?.fail_reason;
|
||||
if (isPenalty || isFailed || isPruned) return;
|
||||
|
||||
const source = t.source || t.user_attrs?.source || 'FEA';
|
||||
const hoverText = `Trial #${t.trial_number}<br>${objectiveName}: ${val.toFixed(4)}<br>Source: ${source}`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user