multimodalart's picture
Upload 121 files
f555806 verified
'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>
</>
);
}