diff --git a/frontend/src/components/SetupPanel.jsx b/frontend/src/components/SetupPanel.jsx new file mode 100644 index 0000000..01f30a7 --- /dev/null +++ b/frontend/src/components/SetupPanel.jsx @@ -0,0 +1,189 @@ +import React, { useState, useEffect, useCallback, useRef } from 'react' +import axios from 'axios' +import { CheckCircle2, XCircle, Loader2, Play, RefreshCw, Server, Cpu, Box, Folder, Wrench, Sparkles } from 'lucide-react' + +const CHECK_LABELS = { + conda: { label: 'Miniconda installed', icon: Box }, + env: { label: 'Conda env "synthetic-data"', icon: Box }, + sdk: { label: 'synthetic-data-kit CLI', icon: Wrench }, + training_deps: { label: 'Training libs (torch/peft/trl)', icon: Sparkles }, + train_py: { label: 'train.py installed', icon: Wrench }, + data_dirs: { label: 'Data directories created', icon: Folder }, + gpu: { label: 'NVIDIA GPU detected', icon: Cpu }, +} + +const CHECK_ORDER = ['gpu', 'conda', 'env', 'sdk', 'training_deps', 'train_py', 'data_dirs'] + +export default function SetupPanel({ connected }) { + const [checks, setChecks] = useState(null) + const [ready, setReady] = useState(false) + const [user, setUser] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState('') + const [running, setRunning] = useState(false) + const [logs, setLogs] = useState([]) + const [stage, setStage] = useState('') + const logRef = useRef(null) + + const runCheck = useCallback(async () => { + if (!connected) return + setLoading(true); setError('') + try { + const { data } = await axios.get('/api/setup/check') + setChecks(data.checks); setReady(data.ready); setUser(data.user) + } catch (e) { + setError(e.response?.data?.detail || e.message) + } finally { + setLoading(false) + } + }, [connected]) + + useEffect(() => { runCheck() }, [runCheck]) + + useEffect(() => { + if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight + }, [logs]) + + const runBootstrap = () => { + if (!connected || running) return + setRunning(true); setLogs([]); setStage('starting'); setError('') + + const proto = location.protocol === 'https:' ? 'wss:' : 'ws:' + const ws = new WebSocket(`${proto}//${location.host}/api/setup/bootstrap`) + + ws.onmessage = (e) => { + const msg = JSON.parse(e.data) + if (msg.type === 'log') { + const line = msg.data + const stageMatch = line.match(/::stage:: (\w+)/) + if (stageMatch) setStage(stageMatch[1]) + setLogs(l => [...l, line]) + } else if (msg.type === 'error') { + setError(msg.data) + setRunning(false) + ws.close() + } else if (msg.type === 'done') { + setStage('done') + setRunning(false) + ws.close() + runCheck() + } + } + ws.onerror = () => { setError('WebSocket error'); setRunning(false) } + ws.onclose = () => setRunning(false) + } + + if (!connected) { + return ( +
Connect to the GPU server first to run setup.
++ Verify and install everything llm-trainer needs on the GPU host + {user && <> — connected as {user}>} +
++ Installs miniconda, creates the conda env, installs synthetic-data-kit + torch + transformers + peft + trl, + creates the data directories, and drops the training script into place. This can take 5–10 minutes + on first run depending on network speed. +
+
+ {logs.join('')}
+
+