From 4ad14d211a969c217bf5470213c04c6052d17592 Mon Sep 17 00:00:00 2001 From: Rasmus Lerdorf Date: Mon, 23 Mar 2026 17:41:27 -0400 Subject: [PATCH] Add an import config button (#733) --- ui/src/app/jobs/new/page.tsx | 63 +++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/ui/src/app/jobs/new/page.tsx b/ui/src/app/jobs/new/page.tsx index 9cce40fa..bbe8042b 100644 --- a/ui/src/app/jobs/new/page.tsx +++ b/ui/src/app/jobs/new/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useSearchParams, useRouter } from 'next/navigation'; import { defaultJobConfig, defaultDatasetConfig, migrateJobConfig } from './jobConfig'; import { jobTypeOptions } from './options'; @@ -11,6 +11,7 @@ import { SelectInput } from '@/components/formInputs'; import useSettings from '@/hooks/useSettings'; import useGPUInfo from '@/hooks/useGPUInfo'; import useDatasetList from '@/hooks/useDatasetList'; +import YAML from 'yaml'; import path from 'path'; import { TopBar, MainContent } from '@/components/layout'; import { Button } from '@headlessui/react'; @@ -36,6 +37,49 @@ export default function TrainingForm() { const [jobConfig, setJobConfig] = useNestedState(objectCopy(defaultJobConfig)); const [status, setStatus] = useState<'idle' | 'saving' | 'success' | 'error'>('idle'); + const fileInputRef = useRef(null); + + const handleImportConfig = () => { + fileInputRef.current?.click(); + }; + + const handleFileSelected = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = () => { + try { + const text = reader.result as string; + let parsed: any; + if (file.name.endsWith('.json') || file.name.endsWith('.jsonc')) { + parsed = JSON.parse(text.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '')); + } else { + parsed = YAML.parse(text); + } + + // Set required fields (same pattern as AdvancedJob.handleChange) + try { + parsed.config.process[0].sqlite_db_path = './aitk_db.db'; + parsed.config.process[0].training_folder = settings.TRAINING_FOLDER; + parsed.config.process[0].device = 'cuda'; + parsed.config.process[0].performance_log_every = 10; + } catch (err) { + console.warn('Could not set required fields on imported config:', err); + } + + migrateJobConfig(parsed); + setJobConfig(parsed); + } catch (err) { + console.error('Failed to parse config file:', err); + alert('Failed to parse config file. Please check the file format.'); + } + }; + reader.readAsText(file); + + // Reset so the same file can be re-imported + e.target.value = ''; + }; useEffect(() => { if (!isSettingsLoaded) return; @@ -161,6 +205,15 @@ export default function TrainingForm() { />
+
+ +
+
)} {!showAdvancedView && ( @@ -215,6 +268,14 @@ export default function TrainingForm() { + + {showAdvancedView ? (