Spaces:
Running
Running
| import { useEffect, useState } from "react"; | |
| import ReactMarkdown from "react-markdown"; | |
| import usageMarkdown from "./usage.md?raw"; | |
| import { | |
| Button, | |
| Card, | |
| Dropdown, | |
| FileUploader, | |
| InputField, | |
| InputSection, | |
| Tabs, | |
| } from "@elvis/ui"; | |
| import { OKABE_ITO_COLORS } from "./colors"; | |
| import { CLASSIFIERS } from "./classifiers"; | |
| import type { CsvSettings, ModelSettings } from "./types"; | |
| import { DATASETS } from "./datasets"; | |
| interface SidebarProps { | |
| pointLabel: string; | |
| setPointLabel: (label: string) => void; | |
| setCanAddPoints: (canAddPoints: boolean) => void; | |
| undoDataPoint: () => void; | |
| clearDataPoints: () => void; | |
| modelSettings: ModelSettings; | |
| setModelSettings: (settings: ModelSettings) => void; | |
| csvSettings: CsvSettings; | |
| onGetDecisionBoundary: () => void; | |
| onCsvUpload: (file: File, settings?: CsvSettings) => void; | |
| onCsvSettingsChange: (settings: CsvSettings) => void; | |
| csvError: string | null; | |
| onCsvExport: () => void; | |
| onSvgExport: () => void; | |
| } | |
| export default function Sidebar({ | |
| pointLabel, | |
| setPointLabel, | |
| setCanAddPoints, | |
| undoDataPoint, | |
| clearDataPoints, | |
| modelSettings, | |
| setModelSettings, | |
| csvSettings, | |
| onGetDecisionBoundary, | |
| onCsvUpload, | |
| onCsvSettingsChange, | |
| csvError, | |
| onCsvExport, | |
| onSvgExport, | |
| }: SidebarProps) { | |
| const tabs = ["Data", "Model", "Export", "Usage"] as const; | |
| const [activeTab, setActiveTab] = useState<(typeof tabs)[number]>("Data"); | |
| const presetOptions = DATASETS.map((dataset) => dataset.name) as string[]; | |
| const customDatasetOptions = ["Manual...", "Upload..."] as string[]; | |
| const [datasetOption, setDatasetOption] = useState<string>("Manual..."); | |
| function isPresetOption(option: string) { | |
| return presetOptions.includes(option); | |
| } | |
| function onDatasetOptionChange(option: string) { | |
| setDatasetOption(option); | |
| if (isPresetOption(option)) { | |
| loadPresetDataset(option); | |
| } | |
| } | |
| async function loadPresetDataset(datasetName: string) { | |
| const dataset = DATASETS.find((d) => d.name === datasetName); | |
| if (!dataset) { | |
| return; | |
| } | |
| const response = await fetch(dataset.url); | |
| const blob = await response.blob(); | |
| const file = new File([blob], `${dataset.name}.csv`, { type: "text/csv" }); | |
| const presetCsvSettings = { | |
| ...csvSettings, | |
| inputColumns: dataset.inputColumns, | |
| outputColumn: dataset.outputColumn, | |
| }; | |
| onCsvUpload(file, presetCsvSettings); | |
| } | |
| useEffect(() => { | |
| clearDataPoints(); | |
| setCanAddPoints(datasetOption === "Manual..."); | |
| }, [datasetOption]); | |
| return ( | |
| <Card className="bg-white flex flex-col h-full min-w-0 min-h-0 overflow-auto border border-gray-200 rounded p-6 gap-4"> | |
| <Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} /> | |
| { /* Tab content */} | |
| <div className="flex flex-col gap-8 min-h-0 overflow-auto"> | |
| { activeTab === "Data" && ( | |
| <> | |
| <InputSection label="Dataset"> | |
| <Dropdown | |
| label="Input" | |
| options={customDatasetOptions} | |
| groupedOptions={[{ label: "Built-in...", options: presetOptions }]} | |
| activeOption={datasetOption} | |
| onChange={onDatasetOptionChange} | |
| /> | |
| { datasetOption === "Manual..." && ( | |
| <> | |
| <Dropdown | |
| label="Point Label" | |
| options={OKABE_ITO_COLORS.map(color => color.name)} | |
| activeOption={pointLabel} | |
| onChange={(option: string) => setPointLabel(option)} | |
| /> | |
| <div className="grid grid-cols-2 gap-2"> | |
| <Button | |
| label="Undo" | |
| onClick={undoDataPoint} | |
| /> | |
| <Button | |
| label="Clear" | |
| onClick={clearDataPoints} | |
| /> | |
| </div> | |
| </> | |
| )} | |
| { datasetOption === "Upload..." && ( | |
| <FileUploader label="Upload CSV" onFileUpload={onCsvUpload} /> | |
| )} | |
| { (datasetOption === "Upload..." || isPresetOption(datasetOption)) && ( | |
| <> | |
| <div className="grid grid-cols-[minmax(0,2fr)_minmax(0,1fr)] gap-2"> | |
| <InputField | |
| label="Input Columns" | |
| value={csvSettings.inputColumns} | |
| onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, inputColumns: value })}} | |
| /> | |
| <InputField | |
| label="Output Column" | |
| value={csvSettings.outputColumn} | |
| onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, outputColumn: value })}} | |
| /> | |
| </div> | |
| </> | |
| )} | |
| </InputSection> | |
| { (datasetOption === "Upload..." || isPresetOption(datasetOption)) && ( | |
| <> | |
| <InputSection label="Preprocessing"> | |
| <Dropdown | |
| label="Normalizer" | |
| options={["None", "MinMax", "Standard"]} | |
| activeOption={csvSettings.normalizerType} | |
| onChange={(option: string) => {onCsvSettingsChange({ ...csvSettings, normalizerType: option })}} | |
| /> | |
| <InputField | |
| label="Normal noise standard deviation" | |
| value={csvSettings.normalNoiseStd} | |
| onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, normalNoiseStd: value })}} | |
| /> | |
| </InputSection> | |
| <InputSection label="Plotting"> | |
| <Dropdown | |
| label="Projection" | |
| options={["Coordinates", "PCA"]} | |
| activeOption={csvSettings.projectionType} | |
| onChange={(option: string) => {onCsvSettingsChange({ ...csvSettings, projectionType: option })}} | |
| /> | |
| {csvSettings.projectionType === "Coordinates" && ( | |
| <div className="grid grid-cols-2 gap-2"> | |
| <InputField | |
| label="X1 Column" | |
| value={csvSettings.x1Column || ""} | |
| onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, x1Column: value })}} | |
| /> | |
| <InputField | |
| label="X2 Column" | |
| value={csvSettings.x2Column || ""} | |
| onChange={(value: string) => {onCsvSettingsChange({ ...csvSettings, x2Column: value })}} | |
| /> | |
| </div> | |
| )} | |
| </InputSection> | |
| {csvError && ( | |
| <InputSection label="Error"> | |
| <p className="text-red-600 font-semibold">CSV Error: {csvError}</p> | |
| </InputSection> | |
| )} | |
| </> | |
| )} | |
| </> | |
| )} | |
| { activeTab === "Model" && ( | |
| <> | |
| <InputSection label="Classifier Options"> | |
| <Dropdown | |
| label="Type" | |
| options={CLASSIFIERS} | |
| activeOption={modelSettings.type} | |
| onChange={(option: string) => { | |
| setModelSettings({ ...modelSettings, type: option }); | |
| }} | |
| /> | |
| <InputField | |
| label="Arguments" | |
| value={modelSettings.arguments} | |
| onChange={(value: string) => { | |
| setModelSettings({ ...modelSettings, arguments: value }); | |
| }} | |
| /> | |
| </InputSection> | |
| <Button | |
| label="Get Decision Boundary" | |
| onClick={onGetDecisionBoundary} | |
| /> | |
| </> | |
| )} | |
| { activeTab === "Export" && ( | |
| <div className="flex flex-col gap-4"> | |
| <Button | |
| label="Dataset as CSV" | |
| onClick={onCsvExport} | |
| /> | |
| <Button | |
| label="Plot as SVG" | |
| onClick={onSvgExport} | |
| /> | |
| {/* <Button | |
| label="Python code" | |
| /> */} | |
| </div> | |
| )} | |
| { activeTab === "Usage" && ( | |
| <div className="prose leading-loose overflow-auto"> | |
| <ReactMarkdown | |
| components={{ | |
| h1: ({ children }) => <h1 className="text-2xl font-bold mt-12 mb-2 first:mt-4">{children}</h1>, | |
| h2: ({ children }) => <h2 className="text-xl font-semibold mt-12 mb-2 first:mt-4">{children}</h2>, | |
| h3: ({ children }) => <h3 className="text-lg font-semibold mt-12 mb-2 first:mt-4">{children}</h3>, | |
| p: ({ children }) => <p className="leading-6 mb-3 last:mb-0">{children}</p>, | |
| ul: ({ children }) => <ul className="list-disc pl-5 mb-3">{children}</ul>, | |
| ol: ({ children }) => <ol className="list-decimal pl-5 mb-3">{children}</ol>, | |
| li: ({ children }) => <li className="mb-1">{children}</li>, | |
| code: ({ children }) => <code className="bg-gray-200 px-1 py-0.5 rounded">{children}</code> | |
| }} | |
| > | |
| {usageMarkdown} | |
| </ReactMarkdown> | |
| </div> | |
| )} | |
| </div> | |
| </Card> | |
| ) | |
| } | |