File size: 8,495 Bytes
f555806
 
3947e91
f555806
 
 
 
 
3947e91
 
f555806
 
3947e91
 
f555806
3947e91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f555806
 
 
 
 
 
 
56abe7f
 
 
5d9965c
 
55edfdd
 
5d9965c
 
 
56abe7f
 
 
b65933f
56abe7f
 
 
 
 
 
 
f555806
3947e91
 
 
 
 
 
 
0e3da48
e704337
3947e91
0e3da48
3947e91
 
 
 
 
 
 
 
 
 
 
e704337
3947e91
 
 
 
 
 
 
 
 
 
 
 
 
e704337
3947e91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0e3da48
 
 
3947e91
0e3da48
3947e91
d4fd92f
3947e91
 
 
56abe7f
 
 
 
 
 
 
f555806
 
56abe7f
 
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
'use client';

import { useEffect, useMemo, useState } from 'react';
import JobsTable from '@/components/JobsTable';
import { TopBar, MainContent } from '@/components/layout';
import Link from 'next/link';
import { useAuth } from '@/contexts/AuthContext';
import HFLoginButton from '@/components/HFLoginButton';
import useSettings from '@/hooks/useSettings';
import { apiClient } from '@/utils/api';

export default function Dashboard() {
  const { status: authStatus, namespace, token: authToken } = useAuth();
  const { settings } = useSettings();
  const isAuthenticated = authStatus === 'authenticated';
  const effectiveToken = useMemo(() => authToken || settings.HF_TOKEN, [authToken, settings.HF_TOKEN]);

  type OrgStatus = 'idle' | 'checking' | 'member' | 'missing' | 'error';
  const [orgStatus, setOrgStatus] = useState<OrgStatus>('idle');

  useEffect(() => {
    if (!isAuthenticated) {
      setOrgStatus('idle');
      return;
    }

    if (!effectiveToken) {
      setOrgStatus('idle');
      return;
    }

    let cancelled = false;
    setOrgStatus('checking');

    apiClient
      .post('/api/hf-hub', {
        action: 'whoami',
        token: effectiveToken,
      })
      .then(response => {
        if (cancelled) return;
        const orgsRaw = response.data?.user?.orgs ?? response.data?.user?.organizations ?? [];
        const REQUIRED_ORG = 'lora-training-frenzi';
        const isMember = Array.isArray(orgsRaw)
          ? orgsRaw.some((org: any) => {
              if (!org) return false;
              if (typeof org === 'string') {
                return org === REQUIRED_ORG;
              }
              const nameMatch = org?.name || org?.organization || org?.namespace || org?.id;
              return nameMatch === REQUIRED_ORG;
            })
          : false;
        setOrgStatus(isMember ? 'member' : 'missing');
      })
      .catch(() => {
        if (!cancelled) {
          setOrgStatus('error');
        }
      });

    return () => {
      cancelled = true;
    };
  }, [effectiveToken, isAuthenticated]);

  return (
    <>
      <TopBar>
        <div>
          <h1 className="text-lg">Dashboard</h1>
        </div>
        <div className="flex-1" />
      </TopBar>
      <MainContent>
        <div className="mb-6">
          <img
            src="https://huggingface.co/spaces/multimodalart/ai-toolkit/resolve/main/lora_frenzi.png"
            alt="LoRA Frenzi event banner"
            className="w-full rounded-lg border border-gray-800"
          />
        </div>
        <div className="border border-gray-800 rounded-xl bg-gray-900 p-6 flex flex-col gap-4">
          <div>
            <h2 className="text-xl font-semibold text-gray-100">
              {isAuthenticated ? `Welcome back, ${namespace || 'creator'}!` : 'Welcome to Ostris AI Toolkit'}
            </h2>
            <p className="text-sm text-gray-400 mt-2">
              {isAuthenticated
                ? 'You are signed in with Hugging Face and can manage jobs, datasets, and submissions.'
                : 'Authenticate with Hugging Face or add a personal access token to create jobs, upload datasets, and launch training.'}
            </p>
          </div>
          {isAuthenticated ? (
            <div className="flex flex-col gap-4 text-sm">
              {orgStatus === 'checking' && (
                <div className="text-xs text-gray-400">Checking your LoRA Frenzi membership…</div>
              )}
              {orgStatus === 'missing' && (
                <div className="border border-yellow-700 bg-yellow-900/20 text-yellow-100 rounded-lg p-4 space-y-2">
                  <p className="font-semibold">LoRA Frenzi reminder</p>
                  <p>
                    Your didn't authorize the <code className="bg-yellow-800/50 px-1 rounded">lora-training-frenzi</code> organization in your login.
                    To participate:
                  </p>
                  <ol className="list-decimal list-inside space-y-1 text-sm">
                    <li>
                      Join the{' '}
                      <a
                        href="https://huggingface.co/organizations/lora-training-frenzi/share/kEyyVNQXBPWqmARdwHFVdIiFqqONHZPOtz"
                        target="_blank"
                        rel="noopener noreferrer"
                        className="text-blue-200 underline"
                      >
                        LoRA Frenzi organization
                      </a>
                      . (if you haven't already)
                    </li>
                    <li>
                      Remove the “Ostris AI Toolkit” app from{' '}
                      <a
                        href="https://huggingface.co/settings/connected-applications"
                        target="_blank"
                        rel="noopener noreferrer"
                        className="text-blue-200 underline"
                      >
                        Hugging Face &gt; Connected Applications
                      </a>
                      .
                    </li>
                    <li>Come back here and sign in again authorizing the lora-training-frenzi organization so we can refresh your permissions.</li>
                  </ol>
                </div>
              )}
              {orgStatus === 'error' && (
                <div className="border border-red-700 bg-red-900/20 text-red-100 rounded-lg p-4 text-sm">
                  We couldn&apos;t verify your organization membership right now. You can retry later or ensure your token is valid.
                </div>
              )}
              <div className="flex flex-wrap items-center gap-3">
                <Link
                  href="/jobs/new"
                  className="px-4 py-2 rounded-md bg-blue-600 hover:bg-blue-500 text-white transition-colors"
                >
                  Create a Training Job
                </Link>
                <Link
                  href="/datasets"
                  className="px-4 py-2 rounded-md bg-gray-800 hover:bg-gray-700 text-gray-200 transition-colors"
                >
                  Manage Datasets
                </Link>
                <Link
                  href="/settings"
                  className="px-4 py-2 rounded-md border border-gray-700 text-gray-300 hover:border-gray-600 transition-colors"
                >
                  Settings
                </Link>
              </div>
            </div>
          ) : (
            <div className="flex flex-col gap-4 text-sm text-gray-300">
              <ol className="list-decimal list-inside space-y-4">
                <li>
                  Join the{' '}
                  <a
                    href="https://huggingface.co/organizations/lora-training-frenzi/share/kEyyVNQXBPWqmARdwHFVdIiFqqONHZPOtz"
                    target="_blank"
                    rel="noopener noreferrer"
                    className="text-blue-400 underline"
                  >
                    LoRA Frenzi organization
                  </a>{' '}
                  first, then authorize the <code className="bg-gray-800 px-1 rounded">lorafrenzi</code> org when prompted.
                  <img
                    src="https://huggingface.co/spaces/multimodalart/ai-toolkit/resolve/main/add_org_to_oauth.png"
                    alt="Authorize lorafrenzi organization"
                    className="mt-3 max-w-sm rounded border border-gray-800"
                  />
                </li>
                <li>
                  <HFLoginButton size="md" />
                </li>
              </ol>
              <Link
                href="/settings"
                className="text-xs text-blue-400 hover:text-blue-300"
              >
                Or manage tokens in Settings
              </Link>
            </div>
          )}
        </div>

        <div className="w-full mt-6">
          <div className="flex justify-between items-center mb-2">
            <h1 className="text-md">Active Jobs</h1>
            <div className="text-xs text-gray-500">
              <Link href="/jobs">View All</Link>
            </div>
          </div>
          {isAuthenticated ? (
            <JobsTable onlyActive />
          ) : (
            <div className="border border-gray-800 rounded-lg p-6 bg-gray-900 text-gray-400 text-sm">
              Sign in with Hugging Face or add an access token in Settings to view and manage jobs.
            </div>
          )}
        </div>
      </MainContent>
    </>
  );
}