Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
| 'use client'; | |
| import { useEffect, useState } from 'react'; | |
| import useSettings from '@/hooks/useSettings'; | |
| import { TopBar, MainContent } from '@/components/layout'; | |
| import { persistSettings } from '@/utils/storage/settingsStorage'; | |
| import { useAuth } from '@/contexts/AuthContext'; | |
| import HFLoginButton from '@/components/HFLoginButton'; | |
| import { useMemo } from 'react'; | |
| import Link from 'next/link'; | |
| export default function Settings() { | |
| const { settings, setSettings } = useSettings(); | |
| const { status: authStatus, namespace, oauthAvailable, loginWithOAuth, logout, setManualToken, error: authError, token: authToken } = useAuth(); | |
| const [status, setStatus] = useState<'idle' | 'saving' | 'success' | 'error'>('idle'); | |
| const [manualToken, setManualTokenInput] = useState(settings.HF_TOKEN || ''); | |
| const isAuthenticated = authStatus === 'authenticated'; | |
| useEffect(() => { | |
| setManualTokenInput(settings.HF_TOKEN || ''); | |
| }, [settings.HF_TOKEN]); | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| setStatus('saving'); | |
| persistSettings(settings) | |
| .then(() => { | |
| setStatus('success'); | |
| }) | |
| .catch(error => { | |
| console.error('Error saving settings:', error); | |
| setStatus('error'); | |
| }) | |
| .finally(() => { | |
| setTimeout(() => setStatus('idle'), 2000); | |
| }); | |
| }; | |
| const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const { name, value } = e.target; | |
| setSettings(prev => ({ ...prev, [name]: value })); | |
| }; | |
| const handleManualSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| await setManualToken(manualToken); | |
| }; | |
| const authDescription = useMemo(() => { | |
| if (authStatus === 'checking') { | |
| return 'Checking your Hugging Face session…'; | |
| } | |
| if (isAuthenticated) { | |
| return `Connected as ${namespace}`; | |
| } | |
| return 'Sign in to use Hugging Face Jobs or submit your own access token.'; | |
| }, [authStatus, isAuthenticated, namespace]); | |
| return ( | |
| <> | |
| <TopBar> | |
| <div> | |
| <h1 className="text-lg">Settings</h1> | |
| </div> | |
| <div className="flex-1"></div> | |
| <div className="flex items-center gap-3 pr-2 text-sm text-gray-400"> | |
| {isAuthenticated ? ( | |
| <span>Welcome, {namespace || 'user'}</span> | |
| ) : ( | |
| <span>Authenticate to unlock training features</span> | |
| )} | |
| </div> | |
| </TopBar> | |
| <MainContent> | |
| <div className="grid gap-4 md:grid-cols-2 mb-6"> | |
| <div className="border border-gray-800 rounded-xl p-5 bg-gray-900"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <div> | |
| <h2 className="text-md font-semibold text-gray-100">Sign in with Hugging Face</h2> | |
| <p className="text-sm text-gray-400 mt-1">{authDescription}</p> | |
| </div> | |
| {isAuthenticated && ( | |
| <span className="text-xs px-2 py-1 rounded-full bg-emerald-900 text-emerald-300">Authenticated</span> | |
| )} | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| {isAuthenticated ? ( | |
| <button | |
| type="button" | |
| onClick={logout} | |
| className="px-4 py-2 rounded-md border border-gray-700 text-sm bg-gray-800 hover:bg-gray-700 transition-colors" | |
| > | |
| Sign out | |
| </button> | |
| ) : ( | |
| <> | |
| <HFLoginButton size="md" className="bg-transparent border-none p-0" /> | |
| {!oauthAvailable && ( | |
| <span className="text-xs text-yellow-500"> | |
| OAuth is unavailable. Set HF_OAUTH_CLIENT_ID/SECRET on the server. | |
| </span> | |
| )} | |
| </> | |
| )} | |
| </div> | |
| {!isAuthenticated && authError && ( | |
| <p className="mt-3 text-xs text-red-400">{authError}</p> | |
| )} | |
| </div> | |
| <form onSubmit={handleManualSubmit} className="border border-gray-800 rounded-xl p-5 bg-gray-900"> | |
| <h2 className="text-md font-semibold text-gray-100">Manual Token</h2> | |
| <p className="text-sm text-gray-400 mt-1"> | |
| Paste an access token created at{' '} | |
| <a href="https://huggingface.co/settings/tokens" target="_blank" rel="noreferrer" className="text-blue-400 hover:text-blue-300"> | |
| huggingface.co/settings/tokens | |
| </a> | |
| . | |
| </p> | |
| <div className="mt-4"> | |
| <input | |
| type="password" | |
| value={manualToken} | |
| onChange={event => setManualTokenInput(event.target.value)} | |
| 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 Hugging Face token" | |
| /> | |
| </div> | |
| <div className="mt-4 flex items-center gap-3"> | |
| <button | |
| type="submit" | |
| className="px-4 py-2 rounded-md bg-blue-600 hover:bg-blue-500 text-sm text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed" | |
| disabled={authStatus === 'checking' || manualToken.trim() === ''} | |
| > | |
| Validate Token | |
| </button> | |
| {isAuthenticated && authToken === manualToken && ( | |
| <span className="text-xs text-emerald-400">Active token</span> | |
| )} | |
| </div> | |
| {authError && ( | |
| <p className="mt-3 text-xs text-red-400">{authError}</p> | |
| )} | |
| </form> | |
| </div> | |
| <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="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> | |
| <div className="space-y-4"> | |
| <h3 className="text-lg font-medium mb-4">Hugging Face Jobs (Cloud Training)</h3> | |
| <div> | |
| <label htmlFor="HF_JOBS_NAMESPACE" className="block text-sm font-medium mb-2"> | |
| HF Jobs Namespace (optional) | |
| <div className="text-gray-500 text-sm ml-1"> | |
| Leave blank to default to the account associated with your Hugging Face token. | |
| </div> | |
| </label> | |
| <input | |
| type="text" | |
| id="HF_JOBS_NAMESPACE" | |
| name="HF_JOBS_NAMESPACE" | |
| value={settings.HF_JOBS_NAMESPACE} | |
| 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="e.g. your-username or your-org" | |
| /> | |
| </div> | |
| <div> | |
| <label htmlFor="HF_JOBS_DEFAULT_HARDWARE" className="block text-sm font-medium mb-2"> | |
| Default Hardware | |
| <div className="text-gray-500 text-sm ml-1"> | |
| Default hardware configuration for cloud training jobs. | |
| </div> | |
| </label> | |
| <select | |
| id="HF_JOBS_DEFAULT_HARDWARE" | |
| name="HF_JOBS_DEFAULT_HARDWARE" | |
| value={settings.HF_JOBS_DEFAULT_HARDWARE} | |
| onChange={(e) => setSettings(prev => ({ ...prev, HF_JOBS_DEFAULT_HARDWARE: e.target.value }))} | |
| 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" | |
| > | |
| <option value="cpu-basic">CPU Basic</option> | |
| <option value="cpu-upgrade">CPU Upgrade</option> | |
| <option value="t4-small">T4 Small</option> | |
| <option value="t4-medium">T4 Medium</option> | |
| <option value="l4x1">L4x1</option> | |
| <option value="l4x4">L4x4</option> | |
| <option value="a10g-small">A10G Small</option> | |
| <option value="a10g-large">A10G Large</option> | |
| <option value="a10g-largex2">A10G Large x2</option> | |
| <option value="a10g-largex4">A10G Large x4</option> | |
| <option value="a100-large">A100 Large</option> | |
| <option value="v5e-1x1">TPU v5e-1x1</option> | |
| <option value="v5e-2x2">TPU v5e-2x2</option> | |
| <option value="v5e-2x4">TPU v5e-2x4</option> | |
| </select> | |
| </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> | |
| </> | |
| ); | |
| } | |