akseljoonas HF Staff commited on
Commit
9ea884b
Β·
1 Parent(s): 71196c0

fix: simplify onboarding to join-then-oauth flow

Browse files

Remove backend org membership check β€” OAuth orgIds handles it.
Onboarding: join org screen β†’ sign in screen with org access note.

backend/routes/agent.py CHANGED
@@ -8,7 +8,7 @@ import logging
8
  import os
9
  from typing import Any
10
 
11
- from dependencies import AUTH_ENABLED, check_org_membership, get_current_user, get_ws_user
12
  from fastapi import (
13
  APIRouter,
14
  Depends,
@@ -35,9 +35,6 @@ logger = logging.getLogger(__name__)
35
 
36
  router = APIRouter(prefix="/api", tags=["agent"])
37
 
38
- REQUIRED_ORG = "ml-agent-explorers"
39
- ORG_JOIN_URL = "https://huggingface.co/organizations/ml-agent-explorers/share/GzPMJUivoFPlfkvFtIqEouZKSytatKQSZT"
40
-
41
  AVAILABLE_MODELS = [
42
  {
43
  "id": "anthropic/claude-opus-4-6",
@@ -217,14 +214,6 @@ async def create_session(
217
  if not hf_token:
218
  hf_token = request.cookies.get("hf_access_token")
219
 
220
- # Gate access behind org membership
221
- if AUTH_ENABLED and hf_token:
222
- if not await check_org_membership(hf_token, REQUIRED_ORG):
223
- raise HTTPException(
224
- status_code=403,
225
- detail={"error": "org_required", "join_url": ORG_JOIN_URL},
226
- )
227
-
228
  try:
229
  session_id = await session_manager.create_session(
230
  user_id=user["user_id"], hf_token=hf_token
 
8
  import os
9
  from typing import Any
10
 
11
+ from dependencies import get_current_user, get_ws_user
12
  from fastapi import (
13
  APIRouter,
14
  Depends,
 
35
 
36
  router = APIRouter(prefix="/api", tags=["agent"])
37
 
 
 
 
38
  AVAILABLE_MODELS = [
39
  {
40
  "id": "anthropic/claude-opus-4-6",
 
214
  if not hf_token:
215
  hf_token = request.cookies.get("hf_access_token")
216
 
 
 
 
 
 
 
 
 
217
  try:
218
  session_id = await session_manager.create_session(
219
  user_id=user["user_id"], hf_token=hf_token
frontend/src/components/WelcomeScreen/WelcomeScreen.tsx CHANGED
@@ -18,48 +18,23 @@ const HF_ORANGE = '#FF9D00';
18
 
19
  const ORG_JOIN_URL = 'https://huggingface.co/organizations/ml-agent-explorers/share/GzPMJUivoFPlfkvFtIqEouZKSytatKQSZT';
20
 
21
- type Step = 'join' | 'signin' | 'ready';
22
-
23
- interface OrgGate {
24
- joinUrl: string;
25
- }
26
-
27
  export default function WelcomeScreen() {
28
  const { createSession } = useSessionStore();
29
  const { setPlan, clearPanel, user } = useAgentStore();
30
  const [isCreating, setIsCreating] = useState(false);
31
  const [error, setError] = useState<string | null>(null);
32
- const [orgGate, setOrgGate] = useState<OrgGate | null>(null);
33
 
34
  const inIframe = isInIframe();
35
  const isAuthenticated = user?.authenticated;
36
  const isDevUser = user?.username === 'dev';
37
 
38
- // Determine which step to show
39
- const step: Step = orgGate
40
- ? 'join' // post-auth safety net: not in org
41
- : (!isAuthenticated && !isDevUser)
42
- ? 'join' // not logged in: start with join
43
- : 'ready'; // authenticated & in org
44
-
45
- // Track whether user has seen the join page and clicked through
46
- const [joinClicked, setJoinClicked] = useState(false);
47
- const showSignin = !isAuthenticated && !isDevUser && joinClicked && !orgGate;
48
-
49
  const tryCreateSession = useCallback(async () => {
50
  setIsCreating(true);
51
  setError(null);
52
 
53
  try {
54
  const response = await apiFetch('/api/session', { method: 'POST' });
55
- if (response.status === 403) {
56
- const data = await response.json();
57
- if (data.detail?.error === 'org_required') {
58
- setOrgGate({ joinUrl: data.detail.join_url });
59
- setJoinClicked(false);
60
- return;
61
- }
62
- }
63
  if (response.status === 503) {
64
  const data = await response.json();
65
  setError(data.detail || 'Server is at capacity. Please try again later.');
@@ -74,7 +49,6 @@ export default function WelcomeScreen() {
74
  return;
75
  }
76
  const data = await response.json();
77
- setOrgGate(null);
78
  createSession(data.session_id);
79
  setPlan([]);
80
  clearPanel();
@@ -122,6 +96,12 @@ export default function WelcomeScreen() {
122
  },
123
  };
124
 
 
 
 
 
 
 
125
  return (
126
  <Box
127
  sx={{
@@ -158,7 +138,7 @@ export default function WelcomeScreen() {
158
  </Typography>
159
 
160
  {/* ── Screen: Join org ─────────────────────────────────────── */}
161
- {step === 'join' && !showSignin && (
162
  <>
163
  <Typography
164
  variant="body1"
@@ -173,19 +153,14 @@ export default function WelcomeScreen() {
173
  '& strong': { color: 'var(--text)', fontWeight: 600 },
174
  }}
175
  >
176
- {orgGate
177
- ? <>Looks like you're not in <strong>ML Agent Explorers</strong> yet. Join the org and sign in again so permissions are granted correctly.</>
178
- : <>Under the hood, this agent uses GPUs, inference APIs, and other paid Hub goodies β€” but we made them all free for you. Just join <strong>ML Agent Explorers</strong> to get started!</>
179
- }
180
  </Typography>
181
 
182
  <Button
183
  variant="contained"
184
  size="large"
185
  component="a"
186
- href={orgGate?.joinUrl || ORG_JOIN_URL}
187
- target="_blank"
188
- rel="noopener noreferrer"
189
  startIcon={<GroupAddIcon />}
190
  sx={primaryBtnSx}
191
  >
@@ -195,14 +170,7 @@ export default function WelcomeScreen() {
195
  <Button
196
  variant="text"
197
  size="small"
198
- onClick={() => {
199
- if (orgGate) {
200
- // Post-auth safety net: need to re-auth to get org permissions
201
- triggerLogin();
202
- } else {
203
- setJoinClicked(true);
204
- }
205
- }}
206
  sx={{
207
  mt: 2,
208
  color: 'var(--muted-text)',
@@ -211,12 +179,12 @@ export default function WelcomeScreen() {
211
  '&:hover': { color: 'var(--text)' },
212
  }}
213
  >
214
- {orgGate ? "I've joined β€” sign in again" : "I've joined β†’"}
215
  </Button>
216
  </>
217
  )}
218
 
219
- {/* ── Screen: Sign in ──────────────────────────────────────── */}
220
  {showSignin && (
221
  <>
222
  <Typography
@@ -231,39 +199,38 @@ export default function WelcomeScreen() {
231
  px: 2,
232
  }}
233
  >
234
- Great! Now sign in with your Hugging Face account to get started.
235
  </Typography>
236
 
237
- {inIframe ? (
238
- <Button
239
- variant="contained"
240
- size="large"
241
- component="a"
242
- href={spaceHost}
243
- target="_blank"
244
- rel="noopener noreferrer"
245
- endIcon={<OpenInNewIcon />}
246
- sx={primaryBtnSx}
247
- >
248
- Open & Sign in
249
- </Button>
250
- ) : (
251
- <Button
252
- variant="contained"
253
- size="large"
254
- onClick={() => triggerLogin()}
255
- sx={primaryBtnSx}
256
- >
257
- Sign in with Hugging Face
258
- </Button>
259
- )}
260
 
261
  <Button
262
  variant="text"
263
  size="small"
264
- onClick={() => setJoinClicked(false)}
265
  sx={{
266
- mt: 2,
267
  color: 'var(--muted-text)',
268
  textTransform: 'none',
269
  fontSize: '0.85rem',
@@ -275,8 +242,45 @@ export default function WelcomeScreen() {
275
  </>
276
  )}
277
 
278
- {/* ── Screen: Start session ────────────────────────────────── */}
279
- {step === 'ready' && (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  <>
281
  <Typography
282
  variant="body1"
 
18
 
19
  const ORG_JOIN_URL = 'https://huggingface.co/organizations/ml-agent-explorers/share/GzPMJUivoFPlfkvFtIqEouZKSytatKQSZT';
20
 
 
 
 
 
 
 
21
  export default function WelcomeScreen() {
22
  const { createSession } = useSessionStore();
23
  const { setPlan, clearPanel, user } = useAgentStore();
24
  const [isCreating, setIsCreating] = useState(false);
25
  const [error, setError] = useState<string | null>(null);
26
+ const [joinedOrg, setJoinedOrg] = useState(false);
27
 
28
  const inIframe = isInIframe();
29
  const isAuthenticated = user?.authenticated;
30
  const isDevUser = user?.username === 'dev';
31
 
 
 
 
 
 
 
 
 
 
 
 
32
  const tryCreateSession = useCallback(async () => {
33
  setIsCreating(true);
34
  setError(null);
35
 
36
  try {
37
  const response = await apiFetch('/api/session', { method: 'POST' });
 
 
 
 
 
 
 
 
38
  if (response.status === 503) {
39
  const data = await response.json();
40
  setError(data.detail || 'Server is at capacity. Please try again later.');
 
49
  return;
50
  }
51
  const data = await response.json();
 
52
  createSession(data.session_id);
53
  setPlan([]);
54
  clearPanel();
 
96
  },
97
  };
98
 
99
+ // Which screen to show
100
+ const showJoinOrg = !isAuthenticated && !isDevUser && !inIframe && !joinedOrg;
101
+ const showSignin = !isAuthenticated && !isDevUser && !inIframe && joinedOrg;
102
+ const showIframe = !isAuthenticated && !isDevUser && inIframe;
103
+ const showReady = isAuthenticated || isDevUser;
104
+
105
  return (
106
  <Box
107
  sx={{
 
138
  </Typography>
139
 
140
  {/* ── Screen: Join org ─────────────────────────────────────── */}
141
+ {showJoinOrg && (
142
  <>
143
  <Typography
144
  variant="body1"
 
153
  '& strong': { color: 'var(--text)', fontWeight: 600 },
154
  }}
155
  >
156
+ Under the hood, this agent uses GPUs, inference APIs, and other paid Hub goodies β€” but we made them all free for you. Just join <strong>ML Agent Explorers</strong> to get started!
 
 
 
157
  </Typography>
158
 
159
  <Button
160
  variant="contained"
161
  size="large"
162
  component="a"
163
+ href={ORG_JOIN_URL}
 
 
164
  startIcon={<GroupAddIcon />}
165
  sx={primaryBtnSx}
166
  >
 
170
  <Button
171
  variant="text"
172
  size="small"
173
+ onClick={() => setJoinedOrg(true)}
 
 
 
 
 
 
 
174
  sx={{
175
  mt: 2,
176
  color: 'var(--muted-text)',
 
179
  '&:hover': { color: 'var(--text)' },
180
  }}
181
  >
182
+ I've already joined β†’
183
  </Button>
184
  </>
185
  )}
186
 
187
+ {/* ── Screen: Sign in (after org join) ─────────────────────── */}
188
  {showSignin && (
189
  <>
190
  <Typography
 
199
  px: 2,
200
  }}
201
  >
202
+ Now sign in with your Hugging Face account to get started.
203
  </Typography>
204
 
205
+ <Button
206
+ variant="contained"
207
+ size="large"
208
+ onClick={() => triggerLogin()}
209
+ sx={primaryBtnSx}
210
+ >
211
+ Sign in with Hugging Face
212
+ </Button>
213
+
214
+ <Typography
215
+ variant="caption"
216
+ sx={{
217
+ mt: 2.5,
218
+ color: 'var(--muted-text)',
219
+ fontSize: '0.78rem',
220
+ textAlign: 'center',
221
+ maxWidth: 360,
222
+ lineHeight: 1.6,
223
+ }}
224
+ >
225
+ Make sure to enable access to the <strong>ml-agent-explorers</strong> org when prompted.
226
+ </Typography>
 
227
 
228
  <Button
229
  variant="text"
230
  size="small"
231
+ onClick={() => setJoinedOrg(false)}
232
  sx={{
233
+ mt: 1.5,
234
  color: 'var(--muted-text)',
235
  textTransform: 'none',
236
  fontSize: '0.85rem',
 
242
  </>
243
  )}
244
 
245
+ {/* ── Screen: Iframe (original) ────────────────────────────── */}
246
+ {showIframe && (
247
+ <>
248
+ <Typography
249
+ variant="body1"
250
+ sx={{
251
+ color: 'var(--muted-text)',
252
+ maxWidth: 520,
253
+ mb: 5,
254
+ lineHeight: 1.8,
255
+ fontSize: '0.95rem',
256
+ textAlign: 'center',
257
+ px: 2,
258
+ '& strong': { color: 'var(--text)', fontWeight: 600 },
259
+ }}
260
+ >
261
+ A general-purpose AI agent for <strong>machine learning engineering</strong>.
262
+ It browses <strong>Hugging Face documentation</strong>, manages{' '}
263
+ <strong>repositories</strong>, launches <strong>training jobs</strong>,
264
+ and explores <strong>datasets</strong> β€” all through natural conversation.
265
+ </Typography>
266
+
267
+ <Button
268
+ variant="contained"
269
+ size="large"
270
+ component="a"
271
+ href={spaceHost}
272
+ target="_blank"
273
+ rel="noopener noreferrer"
274
+ endIcon={<OpenInNewIcon />}
275
+ sx={primaryBtnSx}
276
+ >
277
+ Open HF Agent
278
+ </Button>
279
+ </>
280
+ )}
281
+
282
+ {/* ── Screen: Start session (authenticated) ────────────────── */}
283
+ {showReady && (
284
  <>
285
  <Typography
286
  variant="body1"