File size: 11,618 Bytes
f555806
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
'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>
    </>
  );
}