Make ui more uniform

This commit is contained in:
Jaret Burkett
2025-02-21 06:18:27 -07:00
parent adcf884c0f
commit d0214c0df9
6 changed files with 550 additions and 488 deletions

View File

@@ -1,12 +1,20 @@
'use client'; 'use client';
import GpuMonitor from '@/components/GPUMonitor'; import GpuMonitor from '@/components/GPUMonitor';
import { TopBar, MainContent } from '@/components/layout';
export default function Dashboard() { export default function Dashboard() {
return ( return (
<div className="space-y-6"> <>
<h1 className="text-xl font-bold mb-8">Dashboard</h1> <TopBar>
<GpuMonitor /> <div>
</div> <h1 className="text-lg">Dashboard</h1>
</div>
<div className="flex-1"></div>
</TopBar>
<MainContent>
<GpuMonitor />
</MainContent>
</>
); );
} }

View File

@@ -5,6 +5,7 @@ import { FaChevronLeft } from 'react-icons/fa';
import DatasetImageCard from '@/components/DatasetImageCard'; import DatasetImageCard from '@/components/DatasetImageCard';
import { Button } from '@headlessui/react'; import { Button } from '@headlessui/react';
import AddImagesModal, { openImagesModal } from '@/components/AddImagesModal'; import AddImagesModal, { openImagesModal } from '@/components/AddImagesModal';
import { TopBar, MainContent } from '@/components/layout';
export default function DatasetPage({ params }: { params: { datasetName: string } }) { export default function DatasetPage({ params }: { params: { datasetName: string } }) {
const [imgList, setImgList] = useState<{ img_path: string }[]>([]); const [imgList, setImgList] = useState<{ img_path: string }[]>([]);
@@ -43,7 +44,7 @@ export default function DatasetPage({ params }: { params: { datasetName: string
return ( return (
<> <>
{/* Fixed top bar */} {/* Fixed top bar */}
<div className="absolute top-0 left-0 w-full h-12 dark:bg-gray-900 shadow-sm z-10 flex items-center px-2"> <TopBar>
<div> <div>
<Button className="text-gray-500 dark:text-gray-300 px-3 mt-1" onClick={() => history.back()}> <Button className="text-gray-500 dark:text-gray-300 px-3 mt-1" onClick={() => history.back()}>
<FaChevronLeft /> <FaChevronLeft />
@@ -61,8 +62,8 @@ export default function DatasetPage({ params }: { params: { datasetName: string
Add Images Add Images
</Button> </Button>
</div> </div>
</div> </TopBar>
<div className="pt-16 px-6 absolute top-0 left-0 w-full h-full overflow-auto"> <MainContent>
{status === 'loading' && <p>Loading...</p>} {status === 'loading' && <p>Loading...</p>}
{status === 'error' && <p>Error fetching images</p>} {status === 'error' && <p>Error fetching images</p>}
{status === 'success' && ( {status === 'success' && (
@@ -78,7 +79,7 @@ export default function DatasetPage({ params }: { params: { datasetName: string
))} ))}
</div> </div>
)} )}
</div> </MainContent>
<AddImagesModal /> <AddImagesModal />
</> </>
); );

View File

@@ -8,6 +8,7 @@ import useDatasetList from '@/hooks/useDatasetList';
import { Button } from '@headlessui/react'; import { Button } from '@headlessui/react';
import { FaRegTrashAlt } from 'react-icons/fa'; import { FaRegTrashAlt } from 'react-icons/fa';
import { openConfirm } from '@/components/ConfirmModal'; import { openConfirm } from '@/components/ConfirmModal';
import { TopBar, MainContent } from '@/components/layout';
export default function Datasets() { export default function Datasets() {
const { datasets, status, refreshDatasets } = useDatasetList(); const { datasets, status, refreshDatasets } = useDatasetList();
@@ -15,7 +16,7 @@ export default function Datasets() {
const [isNewDatasetModalOpen, setIsNewDatasetModalOpen] = useState(false); const [isNewDatasetModalOpen, setIsNewDatasetModalOpen] = useState(false);
return ( return (
<> <>
<div className="absolute top-0 left-0 w-full h-12 dark:bg-gray-900 shadow-sm z-10 flex items-center px-2"> <TopBar>
<div> <div>
<h1 className="text-lg">Datasets</h1> <h1 className="text-lg">Datasets</h1>
</div> </div>
@@ -28,8 +29,8 @@ export default function Datasets() {
New Dataset New Dataset
</Button> </Button>
</div> </div>
</div> </TopBar>
<div className="pt-16 px-4 absolute top-0 left-0 w-full h-full overflow-auto"> <MainContent>
{status === 'loading' && <p>Loading...</p>} {status === 'loading' && <p>Loading...</p>}
{status === 'error' && <p>Error fetching datasets</p>} {status === 'error' && <p>Error fetching datasets</p>}
{status === 'success' && ( {status === 'success' && (
@@ -79,7 +80,7 @@ export default function Datasets() {
))} ))}
</div> </div>
)} )}
</div> </MainContent>
<Modal <Modal
isOpen={isNewDatasetModalOpen} isOpen={isNewDatasetModalOpen}
onClose={() => setIsNewDatasetModalOpen(false)} onClose={() => setIsNewDatasetModalOpen(false)}

View File

@@ -2,6 +2,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import useSettings from '@/hooks/useSettings'; import useSettings from '@/hooks/useSettings';
import { TopBar, MainContent } from '@/components/layout';
export default function Settings() { export default function Settings() {
const { settings, setSettings } = useSettings(); const { settings, setSettings } = useSettings();
@@ -37,87 +38,97 @@ export default function Settings() {
}; };
return ( return (
<div className="max-w-2xl mx-auto space-y-6"> <>
<h1 className="text-3xl font-bold mb-8">Settings</h1> <TopBar>
<div>
<form onSubmit={handleSubmit} className="space-y-6"> <h1 className="text-lg">Settings</h1>
<div className="space-y-4">
<div>
<label htmlFor="HF_TOKEN" className="block text-sm font-medium mb-2">
Hugging Face Token
<div className="text-gray-500 text-sm ml-1">
Create a Read token on{' '}
<a href="https://huggingface.co/settings/tokens" target="_blank" rel="noreferrer">
{' '}
Huggingface
</a>{' '}
if you need to access gated/private models.
</div>
</label>
<input
type="password"
id="HF_TOKEN"
name="HF_TOKEN"
value={settings.HF_TOKEN}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-gray-600 focus:border-transparent"
placeholder="Enter your Hugging Face token"
/>
</div>
<div>
<label htmlFor="TRAINING_FOLDER" className="block text-sm font-medium mb-2">
Training Folder Path
<div className="text-gray-500 text-sm ml-1">
We will store your training information here. Must be an absolute path. If blank, it will default to the
output folder in the project root.
</div>
</label>
<input
type="text"
id="TRAINING_FOLDER"
name="TRAINING_FOLDER"
value={settings.TRAINING_FOLDER}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-gray-600 focus:border-transparent"
placeholder="Enter training folder path"
/>
</div>
<div>
<label htmlFor="DATASETS_FOLDER" className="block text-sm font-medium mb-2">
Dataset Folder Path
<div className="text-gray-500 text-sm ml-1">
Where we store and find your datasets.{' '}
<span className="text-orange-800">
Warning: This software may modify datasets so it is recommended you keep a backup somewhere else or
have a dedicated folder for this software.
</span>
</div>
</label>
<input
type="text"
id="DATASETS_FOLDER"
name="DATASETS_FOLDER"
value={settings.DATASETS_FOLDER}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-gray-600 focus:border-transparent"
placeholder="Enter datasets folder path"
/>
</div>
</div> </div>
<div className="flex-1"></div>
</TopBar>
<MainContent>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<div className="space-y-4">
<div>
<label htmlFor="HF_TOKEN" className="block text-sm font-medium mb-2">
Hugging Face Token
<div className="text-gray-500 text-sm ml-1">
Create a Read token on{' '}
<a href="https://huggingface.co/settings/tokens" target="_blank" rel="noreferrer">
{' '}
Huggingface
</a>{' '}
if you need to access gated/private models.
</div>
</label>
<input
type="password"
id="HF_TOKEN"
name="HF_TOKEN"
value={settings.HF_TOKEN}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-gray-600 focus:border-transparent"
placeholder="Enter your Hugging Face token"
/>
</div>
<button <div>
type="submit" <label htmlFor="TRAINING_FOLDER" className="block text-sm font-medium mb-2">
disabled={status === 'saving'} Training Folder Path
className="w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed" <div className="text-gray-500 text-sm ml-1">
> We will store your training information here. Must be an absolute path. If blank, it will default
{status === 'saving' ? 'Saving...' : 'Save Settings'} to the output folder in the project root.
</button> </div>
</label>
<input
type="text"
id="TRAINING_FOLDER"
name="TRAINING_FOLDER"
value={settings.TRAINING_FOLDER}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-gray-600 focus:border-transparent"
placeholder="Enter training folder path"
/>
</div>
{status === 'success' && <p className="text-green-500 text-center">Settings saved successfully!</p>} <div>
{status === 'error' && <p className="text-red-500 text-center">Error saving settings. Please try again.</p>} <label htmlFor="DATASETS_FOLDER" className="block text-sm font-medium mb-2">
</form> Dataset Folder Path
</div> <div className="text-gray-500 text-sm ml-1">
Where we store and find your datasets.{' '}
<span className="text-orange-800">
Warning: This software may modify datasets so it is recommended you keep a backup somewhere else
or have a dedicated folder for this software.
</span>
</div>
</label>
<input
type="text"
id="DATASETS_FOLDER"
name="DATASETS_FOLDER"
value={settings.DATASETS_FOLDER}
onChange={handleChange}
className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-gray-600 focus:border-transparent"
placeholder="Enter datasets folder path"
/>
</div>
</div>
</div>
</div>
<button
type="submit"
disabled={status === 'saving'}
className="w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{status === 'saving' ? 'Saving...' : 'Save Settings'}
</button>
{status === 'success' && <p className="text-green-500 text-center">Settings saved successfully!</p>}
{status === 'error' && <p className="text-red-500 text-center">Error saving settings. Please try again.</p>}
</form>
</MainContent>
</>
); );
} }

View File

@@ -1,7 +1,6 @@
'use client'; 'use client';
// todo update training folder from settings
import { use, useEffect, useMemo, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSearchParams, useRouter } from 'next/navigation'; import { useSearchParams, useRouter } from 'next/navigation';
import { options } from './options'; import { options } from './options';
import { defaultJobConfig, defaultDatasetConfig } from './jobConfig'; import { defaultJobConfig, defaultDatasetConfig } from './jobConfig';
@@ -15,6 +14,7 @@ import useSettings from '@/hooks/useSettings';
import useGPUInfo from '@/hooks/useGPUInfo'; import useGPUInfo from '@/hooks/useGPUInfo';
import useDatasetList from '@/hooks/useDatasetList'; import useDatasetList from '@/hooks/useDatasetList';
import path from 'path'; import path from 'path';
import { TopBar, MainContent } from '@/components/layout';
export default function TrainingForm() { export default function TrainingForm() {
const router = useRouter(); const router = useRouter();
@@ -37,7 +37,7 @@ export default function TrainingForm() {
setDatasetOptions(datasetOptions); setDatasetOptions(datasetOptions);
const defaultDatasetPath = defaultDatasetConfig.folder_path; const defaultDatasetPath = defaultDatasetConfig.folder_path;
for(let i = 0; i < jobConfig.config.process[0].datasets.length; i++) { for (let i = 0; i < jobConfig.config.process[0].datasets.length; i++) {
const dataset = jobConfig.config.process[0].datasets[i]; const dataset = jobConfig.config.process[0].datasets[i];
if (dataset.folder_path === defaultDatasetPath) { if (dataset.folder_path === defaultDatasetPath) {
setJobConfig(datasetOptions[0].value, `config.process[0].datasets[${i}].folder_path`); setJobConfig(datasetOptions[0].value, `config.process[0].datasets[${i}].folder_path`);
@@ -105,218 +105,223 @@ export default function TrainingForm() {
}; };
return ( return (
<div className="space-y-6"> <>
<h1 className="text-xl font-bold mb-8">{runId ? 'Edit Training Run' : 'New Training Run'}</h1> <TopBar>
<div>
<form onSubmit={handleSubmit} className="space-y-8"> <h1 className="text-lg">{runId ? 'Edit Training Run' : 'New Training Run'}</h1>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> </div>
<Card title="Job Settings"> <div className="flex-1"></div>
<TextInput </TopBar>
label="Training Name" <MainContent>
value={jobConfig.config.name} <form onSubmit={handleSubmit} className="space-y-8">
onChange={value => setJobConfig(value, 'config.name')} <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
placeholder="Enter training name" <Card title="Job Settings">
required <TextInput
/> label="Training Name"
<SelectInput value={jobConfig.config.name}
label="GPU ID" onChange={value => setJobConfig(value, 'config.name')}
value={`${gpuID}`} placeholder="Enter training name"
className="pt-2" required
onChange={value => setGpuID(parseInt(value))}
options={gpuList.map(gpu => ({ value: `${gpu}`, label: `GPU #${gpu}` }))}
/>
</Card>
{/* Model Configuration Section */}
<Card title="Model Configuration">
<SelectInput
label="Name or Path"
value={jobConfig.config.process[0].model.name_or_path}
onChange={value => {
// see if model changed
const currentModel = options.model.find(
model => model.name_or_path === jobConfig.config.process[0].model.name_or_path,
);
if (!currentModel || currentModel.name_or_path === value) {
// model has not changed
return;
}
// revert defaults from previous model
for (const key in currentModel.defaults) {
setJobConfig(currentModel.defaults[key][1], key);
}
// set new model
setJobConfig(value, 'config.process[0].model.name_or_path');
// update the defaults when a model is selected
const model = options.model.find(model => model.name_or_path === value);
if (model?.defaults) {
for (const key in model.defaults) {
setJobConfig(model.defaults[key][0], key);
}
}
}}
options={options.model.map(model => ({
value: model.name_or_path,
label: model.name_or_path,
}))}
/>
<FormGroup label="Quantize" className="pt-2">
<Checkbox
label="Transformer"
checked={jobConfig.config.process[0].model.quantize}
onChange={value => setJobConfig(value, 'config.process[0].model.quantize')}
/> />
<Checkbox <SelectInput
label="Text Encoder" label="GPU ID"
checked={jobConfig.config.process[0].model.quantize_te} value={`${gpuID}`}
onChange={value => setJobConfig(value, 'config.process[0].model.quantize_te')} className="pt-2"
onChange={value => setGpuID(parseInt(value))}
options={gpuList.map(gpu => ({ value: `${gpu}`, label: `GPU #${gpu}` }))}
/> />
</FormGroup> </Card>
</Card>
{jobConfig.config.process[0].network?.linear && ( {/* Model Configuration Section */}
<Card title="LoRA Configuration"> <Card title="Model Configuration">
<NumberInput <SelectInput
label="Linear Rank" label="Name or Path"
value={jobConfig.config.process[0].network.linear} value={jobConfig.config.process[0].model.name_or_path}
onChange={value => { onChange={value => {
setJobConfig(value, 'config.process[0].network.linear'); // see if model changed
setJobConfig(value, 'config.process[0].network.linear_alpha'); const currentModel = options.model.find(
model => model.name_or_path === jobConfig.config.process[0].model.name_or_path,
);
if (!currentModel || currentModel.name_or_path === value) {
// model has not changed
return;
}
// revert defaults from previous model
for (const key in currentModel.defaults) {
setJobConfig(currentModel.defaults[key][1], key);
}
// set new model
setJobConfig(value, 'config.process[0].model.name_or_path');
// update the defaults when a model is selected
const model = options.model.find(model => model.name_or_path === value);
if (model?.defaults) {
for (const key in model.defaults) {
setJobConfig(model.defaults[key][0], key);
}
}
}} }}
placeholder="eg. 16" options={options.model.map(model => ({
value: model.name_or_path,
label: model.name_or_path,
}))}
/>
<FormGroup label="Quantize" className="pt-2">
<Checkbox
label="Transformer"
checked={jobConfig.config.process[0].model.quantize}
onChange={value => setJobConfig(value, 'config.process[0].model.quantize')}
/>
<Checkbox
label="Text Encoder"
checked={jobConfig.config.process[0].model.quantize_te}
onChange={value => setJobConfig(value, 'config.process[0].model.quantize_te')}
/>
</FormGroup>
</Card>
{jobConfig.config.process[0].network?.linear && (
<Card title="LoRA Configuration">
<NumberInput
label="Linear Rank"
value={jobConfig.config.process[0].network.linear}
onChange={value => {
setJobConfig(value, 'config.process[0].network.linear');
setJobConfig(value, 'config.process[0].network.linear_alpha');
}}
placeholder="eg. 16"
min={1}
max={1024}
required
/>
</Card>
)}
<Card title="Save Configuration">
<SelectInput
label="Data Type"
value={jobConfig.config.process[0].save.dtype}
onChange={value => setJobConfig(value, 'config.process[0].save.dtype')}
options={[
{ value: 'bf16', label: 'BF16' },
{ value: 'fp16', label: 'FP16' },
{ value: 'fp32', label: 'FP32' },
]}
/>
<NumberInput
label="Save Every"
value={jobConfig.config.process[0].save.save_every}
onChange={value => setJobConfig(value, 'config.process[0].save.save_every')}
placeholder="eg. 250"
min={1}
required
/>
<NumberInput
label="Max Step Saves to Keep"
value={jobConfig.config.process[0].save.max_step_saves_to_keep}
onChange={value => setJobConfig(value, 'config.process[0].save.max_step_saves_to_keep')}
placeholder="eg. 4"
min={1} min={1}
max={1024}
required required
/> />
</Card> </Card>
)} </div>
<Card title="Save Configuration"> <div>
<SelectInput <Card title="Training Configuration">
label="Data Type" <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
value={jobConfig.config.process[0].save.dtype} <div>
onChange={value => setJobConfig(value, 'config.process[0].save.dtype')} <NumberInput
options={[ label="Batch Size"
{ value: 'bf16', label: 'BF16' }, className="pt-2"
{ value: 'fp16', label: 'FP16' }, value={jobConfig.config.process[0].train.batch_size}
{ value: 'fp32', label: 'FP32' }, onChange={value => setJobConfig(value, 'config.process[0].train.batch_size')}
]} placeholder="eg. 4"
/> min={1}
<NumberInput required
label="Save Every" />
value={jobConfig.config.process[0].save.save_every} <NumberInput
onChange={value => setJobConfig(value, 'config.process[0].save.save_every')} label="Gradient Accumulation"
placeholder="eg. 250" className="pt-2"
min={1} value={jobConfig.config.process[0].train.gradient_accumulation}
required onChange={value => setJobConfig(value, 'config.process[0].train.gradient_accumulation')}
/> placeholder="eg. 1"
<NumberInput min={1}
label="Max Step Saves to Keep" required
value={jobConfig.config.process[0].save.max_step_saves_to_keep} />
onChange={value => setJobConfig(value, 'config.process[0].save.max_step_saves_to_keep')} <NumberInput
placeholder="eg. 4" label="Steps"
min={1} className="pt-2"
required value={jobConfig.config.process[0].train.steps}
/> onChange={value => setJobConfig(value, 'config.process[0].train.steps')}
</Card> placeholder="eg. 2000"
</div> min={1}
<div> required
<Card title="Training Configuration"> />
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> </div>
<div> <div>
<NumberInput <SelectInput
label="Batch Size" label="Optimizer"
className="pt-2" className="pt-2"
value={jobConfig.config.process[0].train.batch_size} value={jobConfig.config.process[0].train.optimizer}
onChange={value => setJobConfig(value, 'config.process[0].train.batch_size')} onChange={value => setJobConfig(value, 'config.process[0].train.optimizer')}
placeholder="eg. 4" options={[
min={1} { value: 'adamw8bit', label: 'AdamW8Bit' },
required { value: 'adafactor', label: 'Adafactor' },
/> ]}
<NumberInput />
label="Gradient Accumulation" <NumberInput
className="pt-2" label="Learning Rate"
value={jobConfig.config.process[0].train.gradient_accumulation} className="pt-2"
onChange={value => setJobConfig(value, 'config.process[0].train.gradient_accumulation')} value={jobConfig.config.process[0].train.lr}
placeholder="eg. 1" onChange={value => setJobConfig(value, 'config.process[0].train.lr')}
min={1} placeholder="eg. 0.0001"
required min={0}
/> required
<NumberInput />
label="Steps" <NumberInput
className="pt-2" label="Weight Decay"
value={jobConfig.config.process[0].train.steps} className="pt-2"
onChange={value => setJobConfig(value, 'config.process[0].train.steps')} value={jobConfig.config.process[0].train.optimizer_params.weight_decay}
placeholder="eg. 2000" onChange={value => setJobConfig(value, 'config.process[0].train.optimizer_params.weight_decay')}
min={1} placeholder="eg. 0.0001"
required min={0}
/> required
/>
</div>
</div> </div>
<div> </Card>
<SelectInput </div>
label="Optimizer" <div>
className="pt-2" <Card title="Datasets">
value={jobConfig.config.process[0].train.optimizer} <>
onChange={value => setJobConfig(value, 'config.process[0].train.optimizer')} {jobConfig.config.process[0].datasets.map((dataset, i) => (
options={[ <div key={i} className="p-4 rounded-lg bg-gray-800 relative">
{ value: 'adamw8bit', label: 'AdamW8Bit' }, <button
{ value: 'adafactor', label: 'Adafactor' }, type="button"
]} onClick={() =>
/> setJobConfig(
<NumberInput jobConfig.config.process[0].datasets.filter((_, index) => index !== i),
label="Learning Rate" 'config.process[0].datasets',
className="pt-2" )
value={jobConfig.config.process[0].train.lr} }
onChange={value => setJobConfig(value, 'config.process[0].train.lr')} className="absolute top-2 right-2 bg-red-800 hover:bg-red-700 rounded-full p-1 text-sm transition-colors"
placeholder="eg. 0.0001" >
min={0} <X />
required </button>
/> <h2 className="text-lg font-bold mb-4">Dataset {i + 1}</h2>
<NumberInput <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
label="Weight Decay" <div>
className="pt-2" <SelectInput
value={jobConfig.config.process[0].train.optimizer_params.weight_decay} label="Dataset"
onChange={value => setJobConfig(value, 'config.process[0].train.optimizer_params.weight_decay')} value={dataset.folder_path}
placeholder="eg. 0.0001" onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].folder_path`)}
min={0} options={datasetOptions}
required />
/> {/* <TextInput
</div>
</div>
</Card>
</div>
<div>
<Card title="Datasets">
<>
{jobConfig.config.process[0].datasets.map((dataset, i) => (
<div key={i} className="p-4 rounded-lg bg-gray-800 relative">
<button
type="button"
onClick={() =>
setJobConfig(
jobConfig.config.process[0].datasets.filter((_, index) => index !== i),
'config.process[0].datasets',
)
}
className="absolute top-2 right-2 bg-red-800 hover:bg-red-700 rounded-full p-1 text-sm transition-colors"
>
<X />
</button>
<h2 className="text-lg font-bold mb-4">Dataset {i + 1}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div>
<SelectInput
label="Dataset"
value={dataset.folder_path}
onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].folder_path`)}
options={datasetOptions}
/>
{/* <TextInput
label="Folder Path" label="Folder Path"
value={dataset.folder_path} value={dataset.folder_path}
onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].folder_path`)} onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].folder_path`)}
placeholder="eg. /path/to/images/folder" placeholder="eg. /path/to/images/folder"
required required
/> */} /> */}
{/* <TextInput {/* <TextInput
label="Mask Folder Path" label="Mask Folder Path"
className="pt-2" className="pt-2"
value={dataset.mask_path || ''} value={dataset.mask_path || ''}
@@ -339,211 +344,220 @@ export default function TrainingForm() {
max={1} max={1}
required required
/> */} /> */}
</div> </div>
<div> <div>
<TextInput <TextInput
label="Default Caption" label="Default Caption"
value={dataset.default_caption} value={dataset.default_caption}
onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].default_caption`)} onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].default_caption`)}
placeholder="eg. A photo of a cat" placeholder="eg. A photo of a cat"
/>
<TextInput
label="Caption Extension"
className="pt-2"
value={dataset.caption_ext}
onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].caption_ext`)}
placeholder="eg. txt"
required
/>
<NumberInput
label="Caption Dropout Rate"
className="pt-2"
value={dataset.caption_dropout_rate}
onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].caption_dropout_rate`)}
placeholder="eg. 0.05"
min={0}
required
/>
</div>
<div>
<FormGroup label="Settings" className="">
<Checkbox
label="Cache Latents to Disk"
checked={dataset.cache_latents_to_disk || false}
onChange={value =>
setJobConfig(value, `config.process[0].datasets[${i}].cache_latents_to_disk`)
}
/> />
<Checkbox <TextInput
label="Is Regularization" label="Caption Extension"
className="pt-2" className="pt-2"
checked={dataset.is_reg || false} value={dataset.caption_ext}
onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].is_reg`)} onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].caption_ext`)}
placeholder="eg. txt"
required
/> />
</FormGroup> <NumberInput
label="Caption Dropout Rate"
className="pt-2"
value={dataset.caption_dropout_rate}
onChange={value =>
setJobConfig(value, `config.process[0].datasets[${i}].caption_dropout_rate`)
}
placeholder="eg. 0.05"
min={0}
required
/>
</div>
<div>
<FormGroup label="Settings" className="">
<Checkbox
label="Cache Latents to Disk"
checked={dataset.cache_latents_to_disk || false}
onChange={value =>
setJobConfig(value, `config.process[0].datasets[${i}].cache_latents_to_disk`)
}
/>
<Checkbox
label="Is Regularization"
className="pt-2"
checked={dataset.is_reg || false}
onChange={value => setJobConfig(value, `config.process[0].datasets[${i}].is_reg`)}
/>
</FormGroup>
</div>
<div>
<FormGroup label="Resolutions" className="pt-2">
{[256, 512, 768, 1024, 1280].map(res => (
<Checkbox
key={res}
label={res.toString()}
checked={dataset.resolution.includes(res)}
onChange={value => {
const resolutions = dataset.resolution.includes(res)
? dataset.resolution.filter(r => r !== res)
: [...dataset.resolution, res];
setJobConfig(resolutions, `config.process[0].datasets[${i}].resolution`);
}}
/>
))}
</FormGroup>
</div>
</div>
</div>
))}
<button
type="button"
onClick={() =>
setJobConfig(
[...jobConfig.config.process[0].datasets, objectCopy(defaultDatasetConfig)],
'config.process[0].datasets',
)
}
className="w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors"
>
Add Dataset
</button>
</>
</Card>
</div>
<div>
<Card title="Sample Configuration">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div>
<NumberInput
label="Sample Every"
value={jobConfig.config.process[0].sample.sample_every}
onChange={value => setJobConfig(value, 'config.process[0].sample.sample_every')}
placeholder="eg. 250"
min={1}
required
/>
<SelectInput
label="Sampler"
className="pt-2"
value={jobConfig.config.process[0].sample.sampler}
onChange={value => setJobConfig(value, 'config.process[0].sample.sampler')}
options={[{ value: 'flowmatch', label: 'FlowMatch' }]}
/>
</div>
<div>
<NumberInput
label="Guidance Scale"
value={jobConfig.config.process[0].sample.guidance_scale}
onChange={value => setJobConfig(value, 'config.process[0].sample.guidance_scale')}
placeholder="eg. 1.0"
min={0}
required
/>
<NumberInput
label="Sample Steps"
value={jobConfig.config.process[0].sample.sample_steps}
onChange={value => setJobConfig(value, 'config.process[0].sample.sample_steps')}
placeholder="eg. 1"
className="pt-2"
min={1}
required
/>
</div>
<div>
<NumberInput
label="Width"
value={jobConfig.config.process[0].sample.width}
onChange={value => setJobConfig(value, 'config.process[0].sample.width')}
placeholder="eg. 1024"
min={256}
required
/>
<NumberInput
label="Height"
value={jobConfig.config.process[0].sample.height}
onChange={value => setJobConfig(value, 'config.process[0].sample.height')}
placeholder="eg. 1024"
className="pt-2"
min={256}
required
/>
</div>
<div>
<NumberInput
label="Seed"
value={jobConfig.config.process[0].sample.seed}
onChange={value => setJobConfig(value, 'config.process[0].sample.seed')}
placeholder="eg. 0"
min={0}
required
/>
<Checkbox
label="Walk Seed"
className="pt-4 pl-2"
checked={jobConfig.config.process[0].sample.walk_seed}
onChange={value => setJobConfig(value, 'config.process[0].sample.walk_seed')}
/>
</div>
</div>
<FormGroup
label={`Sample Prompts (${jobConfig.config.process[0].sample.prompts.length})`}
className="pt-2"
>
{jobConfig.config.process[0].sample.prompts.map((prompt, i) => (
<div key={i} className="flex items-center space-x-2">
<div className="flex-1">
<TextInput
value={prompt}
onChange={value => setJobConfig(value, `config.process[0].sample.prompts[${i}]`)}
placeholder="Enter prompt"
required
/>
</div> </div>
<div> <div>
<FormGroup label="Resolutions" className="pt-2"> <button
{[256, 512, 768, 1024, 1280].map(res => ( type="button"
<Checkbox onClick={() =>
key={res} setJobConfig(
label={res.toString()} jobConfig.config.process[0].sample.prompts.filter((_, index) => index !== i),
checked={dataset.resolution.includes(res)} 'config.process[0].sample.prompts',
onChange={value => { )
const resolutions = dataset.resolution.includes(res) }
? dataset.resolution.filter(r => r !== res) className="rounded-full p-1 text-sm"
: [...dataset.resolution, res]; >
setJobConfig(resolutions, `config.process[0].datasets[${i}].resolution`); <X />
}} </button>
/>
))}
</FormGroup>
</div> </div>
</div> </div>
</div> ))}
))} <button
<button type="button"
type="button" onClick={() =>
onClick={() => setJobConfig(
setJobConfig( [...jobConfig.config.process[0].sample.prompts, ''],
[...jobConfig.config.process[0].datasets, objectCopy(defaultDatasetConfig)], 'config.process[0].sample.prompts',
'config.process[0].datasets', )
) }
} className="w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors"
className="w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors" >
> Add Prompt
Add Dataset </button>
</button> </FormGroup>
</> </Card>
</Card> </div>
</div>
<div>
<Card title="Sample Configuration">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div>
<NumberInput
label="Sample Every"
value={jobConfig.config.process[0].sample.sample_every}
onChange={value => setJobConfig(value, 'config.process[0].sample.sample_every')}
placeholder="eg. 250"
min={1}
required
/>
<SelectInput
label="Sampler"
className="pt-2"
value={jobConfig.config.process[0].sample.sampler}
onChange={value => setJobConfig(value, 'config.process[0].sample.sampler')}
options={[{ value: 'flowmatch', label: 'FlowMatch' }]}
/>
</div>
<div>
<NumberInput
label="Guidance Scale"
value={jobConfig.config.process[0].sample.guidance_scale}
onChange={value => setJobConfig(value, 'config.process[0].sample.guidance_scale')}
placeholder="eg. 1.0"
min={0}
required
/>
<NumberInput
label="Sample Steps"
value={jobConfig.config.process[0].sample.sample_steps}
onChange={value => setJobConfig(value, 'config.process[0].sample.sample_steps')}
placeholder="eg. 1"
className="pt-2"
min={1}
required
/>
</div>
<div>
<NumberInput
label="Width"
value={jobConfig.config.process[0].sample.width}
onChange={value => setJobConfig(value, 'config.process[0].sample.width')}
placeholder="eg. 1024"
min={256}
required
/>
<NumberInput
label="Height"
value={jobConfig.config.process[0].sample.height}
onChange={value => setJobConfig(value, 'config.process[0].sample.height')}
placeholder="eg. 1024"
className="pt-2"
min={256}
required
/>
</div>
<div> <button
<NumberInput type="submit"
label="Seed" disabled={status === 'saving'}
value={jobConfig.config.process[0].sample.seed} className="w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
onChange={value => setJobConfig(value, 'config.process[0].sample.seed')} >
placeholder="eg. 0" {status === 'saving' ? 'Saving...' : runId ? 'Update Training' : 'Create Training'}
min={0} </button>
required
/>
<Checkbox
label="Walk Seed"
className="pt-4 pl-2"
checked={jobConfig.config.process[0].sample.walk_seed}
onChange={value => setJobConfig(value, 'config.process[0].sample.walk_seed')}
/>
</div>
</div>
<FormGroup label={`Sample Prompts (${jobConfig.config.process[0].sample.prompts.length})`} className="pt-2">
{jobConfig.config.process[0].sample.prompts.map((prompt, i) => (
<div key={i} className="flex items-center space-x-2">
<div className="flex-1">
<TextInput
value={prompt}
onChange={value => setJobConfig(value, `config.process[0].sample.prompts[${i}]`)}
placeholder="Enter prompt"
required
/>
</div>
<div>
<button
type="button"
onClick={() =>
setJobConfig(
jobConfig.config.process[0].sample.prompts.filter((_, index) => index !== i),
'config.process[0].sample.prompts',
)
}
className="rounded-full p-1 text-sm"
>
<X />
</button>
</div>
</div>
))}
<button
type="button"
onClick={() =>
setJobConfig([...jobConfig.config.process[0].sample.prompts, ''], 'config.process[0].sample.prompts')
}
className="w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors"
>
Add Prompt
</button>
</FormGroup>
</Card>
</div>
<button {status === 'success' && <p className="text-green-500 text-center">Training saved successfully!</p>}
type="submit" {status === 'error' && <p className="text-red-500 text-center">Error saving training. Please try again.</p>}
disabled={status === 'saving'} </form>
className="w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed" </MainContent>
> </>
{status === 'saving' ? 'Saving...' : runId ? 'Update Training' : 'Create Training'}
</button>
{status === 'success' && <p className="text-green-500 text-center">Training saved successfully!</p>}
{status === 'error' && <p className="text-red-500 text-center">Error saving training. Please try again.</p>}
</form>
</div>
); );
} }

View File

@@ -0,0 +1,27 @@
import classNames from 'classnames';
interface Props {
className?: string;
children?: React.ReactNode;
}
export const TopBar: React.FC<Props> = ({ children, className }) => {
return (
<div
className={classNames(
'absolute top-0 left-0 w-full h-12 dark:bg-gray-900 shadow-sm z-10 flex items-center px-2',
className,
)}
>
{children ? children : null}
</div>
);
};
export const MainContent: React.FC<Props> = ({ children, className }) => {
return (
<div className={classNames('pt-16 px-4 absolute top-0 left-0 w-full h-full overflow-auto', className)}>
{children ? children : null}
</div>
);
};