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:
2025-12-20 13:47:05 -05:00
parent 1612991d0d
commit 7c700c4606
19 changed files with 478 additions and 173 deletions

View File

@@ -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);

View File

@@ -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 [];

View File

@@ -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}

View File

@@ -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';
}}
/>
),
}}

View File

@@ -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>

View File

@@ -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}`;