Spaces:
Running
Running
Download frontends/react/src/Sidebar.tsx from elvis-hf/optimization: direct link, hf CLI and curl.
- Browser
- Download file 16.6 kB
-
https://huggingface.co/spaces/elvis-hf/optimization/resolve/main/frontends/react/src/Sidebar.tsx
- Command line
-
hf download hf://spaces/elvis-hf/optimization/frontends/react/src/Sidebar.tsx
-
curl -L -o Sidebar.tsx https://huggingface.co/spaces/elvis-hf/optimization/resolve/main/frontends/react/src/Sidebar.tsx
16.6 kB
| 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<string>("--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<T>(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<number | null>(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 ( | |
| <Card className="flex flex-col h-full p-4 gap-2 min-w-0 min-h-0 overflow-auto"> | |
| <div className="w-full [&>div]:w-full [&>div>button]:w-1/4 [&>div>button]:px-1"> | |
| <Tabs tabs={tabs} activeTab={activeTab} onChange={(tab) => { setActiveTab(tab); onTabChange(tab); }} /> | |
| </div> | |
| {/* Tab content */} | |
| <div className="flex flex-1 flex-col gap-4 min-h-0 min-w-0 overflow-auto"> | |
| {activeTab === "Settings" && ( | |
| <> | |
| <div className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-0.5"> | |
| <label className="text-sm font-semibold whitespace-nowrap pr-0.25">Objective</label> | |
| <div className="min-w-0"> | |
| <select | |
| value={`${settings.mode}::${functionOption}`} | |
| onChange={(event) => handleProblemSettingChange(event.target.value)} | |
| className="h-10 w-full rounded border border-stone-300 bg-white px-2 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-stone-400" | |
| > | |
| <optgroup label="Univariate" className="font-semibold"> | |
| {Object.keys(UNIVARIATE_FUNCTION_OPTIONS).map((option) => ( | |
| <option key={`Univariate::${option}`} value={`Univariate::${option}`}> | |
| {option} | |
| </option> | |
| ))} | |
| </optgroup> | |
| <optgroup label="Bivariate" className="font-semibold"> | |
| {Object.keys(BIVARIATE_FUNCTION_OPTIONS).map((option) => ( | |
| <option key={`Bivariate::${option}`} value={`Bivariate::${option}`}> | |
| {option} | |
| </option> | |
| ))} | |
| </optgroup> | |
| </select> | |
| </div> | |
| </div> | |
| <InputField | |
| label="" | |
| value={settings.functionExpr} | |
| onChange={(value) => updateSettings("functionExpr", value)} | |
| readonly={functionOption !== "--Custom--"} | |
| rows={3} | |
| /> | |
| {settings.mode === "Bivariate" && ( | |
| <fieldset className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-0.5"> | |
| <legend className="text-sm font-medium whitespace-nowrap pr-0.25">Gradient field</legend> | |
| <div className="flex items-center gap-4 text-sm"> | |
| <label className="flex cursor-pointer items-center gap-1.5"> | |
| <input | |
| type="radio" | |
| name="gradient-field" | |
| checked={showGradientField} | |
| onChange={() => onShowGradientFieldChange(true)} | |
| /> | |
| Show | |
| </label> | |
| <label className="flex cursor-pointer items-center gap-1.5"> | |
| <input | |
| type="radio" | |
| name="gradient-field" | |
| checked={!showGradientField} | |
| onChange={() => onShowGradientFieldChange(false)} | |
| /> | |
| Hide | |
| </label> | |
| </div> | |
| </fieldset> | |
| )} | |
| <AlgorithmControls settings={settings} setSettings={setSettings} onRandomInitialPoint={onRandomInitialPoint} /> | |
| </> | |
| )} | |
| {activeTab === "Optimize" && ( | |
| <> | |
| <IterationHistoryPlot mode={settings.mode} trajectoryValues={trajectoryValues} currentIteration={currentIteration} /> | |
| <div className="hidden lg:flex flex-none flex-col gap-1 min-w-0"> | |
| <div className="flex items-center justify-between gap-2 text-sm font-semibold"> | |
| <span>Current values</span> | |
| <span className="whitespace-nowrap font-normal text-stone-500"> | |
| Iteration {currentIteration} of {totalIterations} | |
| </span> | |
| </div> | |
| { settings.mode === "Univariate" && ( | |
| <> | |
| <div className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-2"> | |
| <label className="text-right text-sm font-medium whitespace-nowrap">X</label> | |
| <input | |
| type="text" | |
| value={currentX !== null ? currentX.toFixed(4) : ""} | |
| readOnly | |
| className="w-full rounded border border-stone-300 bg-stone-50 px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-2"> | |
| <label className="text-right text-sm font-medium whitespace-nowrap">Y</label> | |
| <input | |
| type="text" | |
| value={currentY !== null ? currentY.toFixed(4) : ""} | |
| readOnly | |
| className="w-full rounded border border-stone-300 bg-stone-50 px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-2"> | |
| <label className="text-right text-sm font-medium whitespace-nowrap">1st derivative</label> | |
| <input | |
| type="text" | |
| value={currentDerivative !== null ? currentDerivative.toFixed(4) : ""} | |
| readOnly | |
| className="w-full rounded border border-stone-300 bg-stone-50 px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-2"> | |
| <label className="text-right text-sm font-medium whitespace-nowrap">2nd derivative</label> | |
| <input | |
| type="text" | |
| value={currentSecondDerivative !== null ? currentSecondDerivative.toFixed(4) : ""} | |
| readOnly | |
| className="w-full rounded border border-stone-300 bg-stone-50 px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| </> | |
| )} | |
| { settings.mode === "Bivariate" && ( | |
| <> | |
| <div className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-2"> | |
| <label className="text-right text-sm font-medium whitespace-nowrap">(X, Y)</label> | |
| <input | |
| type="text" | |
| value={currentX !== null && currentY !== null ? `${currentX.toFixed(4)}, ${currentY.toFixed(4)}` : ""} | |
| readOnly | |
| className="w-full rounded border border-stone-300 bg-stone-50 px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-2"> | |
| <label className="text-right text-sm font-medium whitespace-nowrap">Z</label> | |
| <input | |
| type="text" | |
| value={currentZ !== null ? currentZ.toFixed(4) : ""} | |
| readOnly | |
| className="w-full rounded border border-stone-300 bg-stone-50 px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-2"> | |
| <label className="text-right text-sm font-medium whitespace-nowrap">Gradient</label> | |
| <input | |
| type="text" | |
| value={currentGradient !== null ? `[${currentGradient.map(v => v.toFixed(4)).join(", ")}]` : ""} | |
| readOnly | |
| className="w-full rounded border border-stone-300 bg-stone-50 px-2 py-1 text-sm" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-[6rem_minmax(0,1fr)] items-center gap-2"> | |
| <label className="text-right text-sm font-medium whitespace-nowrap">Hessian</label> | |
| <input | |
| type="text" | |
| value={currentHessian !== null ? `[${currentHessian.map(row => `[${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" | |
| /> | |
| </div> | |
| </> | |
| )} | |
| </div> | |
| <div className="-mt-2 flex flex-col gap-1"> | |
| <div className="flex gap-2"> | |
| <button | |
| type="button" | |
| onPointerDown={() => startHolding(onNextStep)} | |
| onPointerUp={stopHolding} | |
| onPointerCancel={stopHolding} | |
| onPointerLeave={stopHolding} | |
| disabled={!onNextStep} | |
| className="h-8 flex-1 rounded border border-gray-300 bg-orange-200 px-3 py-1 text-sm text-stone-700 hover:bg-orange-300 disabled:cursor-not-allowed disabled:opacity-50" | |
| > | |
| Next | |
| </button> | |
| <button | |
| type="button" | |
| onPointerDown={() => startHolding(onPrevStep)} | |
| onPointerUp={stopHolding} | |
| onPointerCancel={stopHolding} | |
| onPointerLeave={stopHolding} | |
| className="h-8 flex-1 rounded border border-gray-300 bg-orange-200 px-3 py-1 text-sm text-stone-700 hover:bg-orange-300" | |
| > | |
| Previous | |
| </button> | |
| <button | |
| type="button" | |
| onClick={onReset} | |
| className="h-8 flex-1 rounded border border-gray-300 bg-orange-200 px-3 py-1 text-sm text-stone-700 hover:bg-orange-300" | |
| > | |
| Reset | |
| </button> | |
| </div> | |
| <div className="grid grid-cols-3 gap-2"> | |
| <button | |
| type="button" | |
| onClick={onAnimate} | |
| disabled={isAnimating || !onAnimate} | |
| className="h-8 rounded border border-gray-300 bg-orange-200 px-3 py-1 text-sm text-stone-700 hover:bg-orange-300 disabled:cursor-not-allowed disabled:opacity-50" | |
| > | |
| Animate | |
| </button> | |
| <button | |
| type="button" | |
| onClick={onPause} | |
| disabled={!isAnimating} | |
| className="h-8 rounded border border-gray-300 bg-stone-100 px-3 py-1 text-sm text-stone-700 hover:bg-stone-200 disabled:cursor-not-allowed disabled:opacity-50" | |
| > | |
| Pause | |
| </button> | |
| </div> | |
| </div> | |
| </> | |
| )} | |
| {activeTab === "Compare" && comparison} | |
| {activeTab === "Usage" && ( | |
| <ReactMarkdown | |
| components={{ | |
| h1: ({ children }) => <h1 className="text-2xl font-bold mt-0 -mb-2">{children}</h1>, | |
| h2: ({ children }) => <h2 className="text-xl font-semibold mt-0 -mb-2">{children}</h2>, | |
| h3: ({ children }) => <h3 className="text-lg font-semibold mt-0 -mb-2">{children}</h3>, | |
| p: ({ children }) => <p className="leading-6 mb-3 last:mb-0">{children}</p>, | |
| ul: ({ children }) => <ul className="-mt-6 mb-0 list-disc pl-4 [&_p]:mb-0">{children}</ul>, | |
| ol: ({ children }) => <ol className="-mt-5 mb-0 list-decimal pl-4 [&_p]:mb-0">{children}</ol>, | |
| li: ({ children }) => <li className="mb-0">{children}</li>, | |
| }} | |
| > | |
| {usageMarkdown} | |
| </ReactMarkdown> | |
| )} | |
| </div> | |
| </Card> | |
| ); | |
| } | |