import type { ReactNode } from "react"; import AlgorithmControls from "./AlgorithmControls"; import { useCallback, useEffect, useRef, useState } from "react"; import ReactMarkdown from "react-markdown"; import { InputField, Tabs, Card } from "@elvis/ui"; import { type SettingsUi, type TrajectoryValues } from "./types.ts"; import usageMarkdown from "./usage.md?raw"; import IterationHistoryPlot from "./IterationHistoryPlot.tsx"; const UNIVARIATE_FUNCTION_OPTIONS = { "--Custom--": "x^2", "Quadratic": "x^2", "Local Minima": "x^2 - 0.1cos(20x)", "Plateau": "x^4", } const BIVARIATE_FUNCTION_OPTIONS = { "--Custom--": "x^2 + 3y^2", "Quadratic": "x^2 + 3y^2", "Small": "(0.05x)^2 + 3(0.05y)^2", "Ackley": "-20exp(-0.2 sqrt(0.5(x^2 + y^2))) - exp(0.5(cos(2 pi x) + cos(2 pi y))) + e + 20", "Rasteringin": "20 + (x^2 - 10cos(2 pi x)) + (y^2 - 10cos(2 pi y))", "Rosenbrock": "(1 - x)^2 + 100(y - x^2)^2", } interface SidebarProps { comparison: ReactNode, onTabChange: (tab: string) => void, settings: SettingsUi, setSettings: (settings: SettingsUi) => void, onRandomInitialPoint: () => void, trajectoryValues?: TrajectoryValues | null, onReset?: () => void, onNextStep?: () => void, onPrevStep?: () => void, onAnimate?: () => void, onPause?: () => void, isAnimating?: boolean, currentIteration: number, totalIterations: number, showGradientField: boolean, onShowGradientFieldChange: (show: boolean) => void, } export default function Sidebar({ comparison, onTabChange, settings, setSettings, onRandomInitialPoint, trajectoryValues, onReset, onNextStep, onPrevStep, onAnimate, onPause, isAnimating = false, currentIteration, totalIterations, showGradientField, onShowGradientFieldChange, }: SidebarProps) { const tabs = ["Settings", "Optimize", "Compare", "Usage"] as const; const [activeTab, setActiveTab] = useState<(typeof tabs)[number]>("Settings"); const [functionOption, setFunctionOption] = useState("--Custom--"); function updateSettings(key: keyof SettingsUi, value: string) { setSettings({ ...settings, [key]: value }); } function handleProblemSettingChange(value: string) { const [mode, option] = value.split("::") as [SettingsUi["mode"], string]; const selectedOption = option ?? "--Custom--"; const expr = mode === "Bivariate" ? BIVARIATE_FUNCTION_OPTIONS[selectedOption as keyof typeof BIVARIATE_FUNCTION_OPTIONS] : UNIVARIATE_FUNCTION_OPTIONS[selectedOption as keyof typeof UNIVARIATE_FUNCTION_OPTIONS]; setFunctionOption(selectedOption); setSettings({ ...settings, mode, functionExpr: expr }); } function getCurrentValue(values?: T[] | null): T | null { return values?.[currentIteration] ?? null; } const currentX = getCurrentValue(trajectoryValues?.x); const currentY = getCurrentValue(trajectoryValues?.y); // univariate only const currentDerivative = getCurrentValue(trajectoryValues?.derivative); const currentSecondDerivative = getCurrentValue(trajectoryValues?.secondDerivative); // bivariate only const currentZ = getCurrentValue(trajectoryValues?.z); const currentGradient = getCurrentValue(trajectoryValues?.gradient); const currentHessian = getCurrentValue(trajectoryValues?.hessian); const holdIntervalRef = useRef(null); const stopHolding = useCallback(() => { if (holdIntervalRef.current !== null) { window.clearInterval(holdIntervalRef.current); holdIntervalRef.current = null; } }, []); const startHolding = useCallback((action?: () => void) => { if (!action || holdIntervalRef.current !== null) return; action(); holdIntervalRef.current = window.setInterval(action, 200); }, []); useEffect(() => { function handleKeyDown(event: KeyboardEvent) { if (activeTab !== "Optimize") return; const target = event.target as HTMLElement | null; if (target?.matches("input, textarea, select, [contenteditable='true']")) return; if (event.repeat) return; if (event.key === "ArrowLeft") { event.preventDefault(); startHolding(onPrevStep); } else if (event.key === "ArrowRight") { event.preventDefault(); startHolding(onNextStep); } } function handleKeyUp(event: KeyboardEvent) { if (event.key === "ArrowLeft" || event.key === "ArrowRight") stopHolding(); } window.addEventListener("keydown", handleKeyDown); window.addEventListener("keyup", handleKeyUp); window.addEventListener("blur", stopHolding); return () => { stopHolding(); window.removeEventListener("keydown", handleKeyDown); window.removeEventListener("keyup", handleKeyUp); window.removeEventListener("blur", stopHolding); }; }, [activeTab, onNextStep, onPrevStep, startHolding, stopHolding]); return (
{ setActiveTab(tab); onTabChange(tab); }} />
{/* Tab content */}
{activeTab === "Settings" && ( <>
updateSettings("functionExpr", value)} readonly={functionOption !== "--Custom--"} rows={3} /> {settings.mode === "Bivariate" && (
Gradient field
)} )} {activeTab === "Optimize" && ( <>
Current values Iteration {currentIteration} of {totalIterations}
{ settings.mode === "Univariate" && ( <>
)} { settings.mode === "Bivariate" && ( <>
v.toFixed(4)).join(", ")}]` : ""} readOnly className="w-full rounded border border-stone-300 bg-stone-50 px-2 py-1 text-sm" />
`[${row.map(v => v.toFixed(4)).join(", ")}]`).join(", ")}]` : ""} readOnly className="w-full rounded border border-stone-300 bg-stone-50 px-2 py-1 text-sm" />
)}
)} {activeTab === "Compare" && comparison} {activeTab === "Usage" && (

{children}

, h2: ({ children }) =>

{children}

, h3: ({ children }) =>

{children}

, p: ({ children }) =>

{children}

, ul: ({ children }) =>
    {children}
, ol: ({ children }) =>
    {children}
, li: ({ children }) =>
  • {children}
  • , }} > {usageMarkdown}
    )}
    ); }