BladeSzaSza Claude commited on
Commit
5dda871
·
1 Parent(s): 96aeabf

Implement clean OAuth login with gr.LoginButton and improve UI design

Browse files

- Add hf_oauth: true to README.md for HuggingFace Spaces OAuth integration
- Replace custom token input with official gr.LoginButton for seamless authentication
- Remove cluttered UI elements: instructions dropdown, unnecessary labels, tab bar when logged out
- Hide tabs until user is authenticated, show clean login screen first
- Implement OAuth flow with proper user creation and session management
- Extract user info from OAuth request context (preferred_username, sub, email, etc.)
- Maintain backward compatibility with existing user database structure
- Clean, minimal login interface that matches modern OAuth patterns

This provides a much cleaner, professional authentication experience.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>

Files changed (2) hide show
  1. README.md +1 -0
  2. digipal/ui/gradio_interface.py +150 -44
README.md CHANGED
@@ -8,6 +8,7 @@ app_port: 7860
8
  pinned: false
9
  license: mit
10
  short_description: AI-powered digital pet inspired by Digimon World 1
 
11
  ---
12
 
13
  # 🥚 DigiPal - Your AI Digital Pet
 
8
  pinned: false
9
  license: mit
10
  short_description: AI-powered digital pet inspired by Digimon World 1
11
+ hf_oauth: true
12
  ---
13
 
14
  # 🥚 DigiPal - Your AI Digital Pet
digipal/ui/gradio_interface.py CHANGED
@@ -65,12 +65,12 @@ class GradioInterface:
65
  user_state = gr.State(None)
66
  token_state = gr.State(None)
67
 
68
- # Main interface tabs with proper tab switching
69
- with gr.Tabs(selected="auth_tab") as main_tabs:
70
-
71
- # Authentication Tab
72
- with gr.Tab("Login", id="auth_tab") as auth_tab:
73
- auth_components = self._create_authentication_tab()
74
 
75
  # Egg Selection Tab
76
  with gr.Tab("Choose Your Egg", id="egg_tab") as egg_tab:
@@ -83,7 +83,7 @@ class GradioInterface:
83
  # Event handlers
84
  self._setup_event_handlers(
85
  auth_components, egg_components, main_components,
86
- user_state, token_state, main_tabs
87
  )
88
 
89
  self.app = interface
@@ -104,19 +104,10 @@ class GradioInterface:
104
  with gr.Column(scale=1):
105
  pass # Empty column for centering
106
 
107
- with gr.Column(scale=2, elem_classes=["auth-form"]):
108
- gr.HTML("<h3>Login with HuggingFace</h3>")
109
-
110
- token_input = gr.Textbox(
111
- label="HuggingFace Token",
112
- placeholder="Enter your HuggingFace token...",
113
- type="password",
114
- elem_classes=["token-input"]
115
- )
116
-
117
- login_btn = gr.Button(
118
- "Login",
119
- variant="primary",
120
  elem_classes=["login-btn"]
121
  )
122
 
@@ -128,24 +119,8 @@ class GradioInterface:
128
  with gr.Column(scale=1):
129
  pass # Empty column for centering
130
 
131
- # Instructions
132
- gr.HTML("""
133
- <details class="instructions-details">
134
- <summary>How to get a HuggingFace Token</summary>
135
- <div class="instructions">
136
- <ol>
137
- <li>Go to <a href="https://huggingface.co/settings/tokens" target="_blank">HuggingFace Tokens</a></li>
138
- <li>Click "New token"</li>
139
- <li>Give it a name like "DigiPal"</li>
140
- <li>Select "Read" permissions</li>
141
- <li>Copy the token and paste it above</li>
142
- </ol>
143
- </div>
144
- </details>
145
- """)
146
 
147
  return {
148
- 'token_input': token_input,
149
  'login_btn': login_btn,
150
  'auth_status': auth_status
151
  }
@@ -414,14 +389,13 @@ class GradioInterface:
414
 
415
  def _setup_event_handlers(self, auth_components: Dict, egg_components: Dict,
416
  main_components: Dict, user_state: gr.State,
417
- token_state: gr.State, main_tabs: gr.Tabs):
418
  """Set up event handlers for all interface components."""
419
 
420
  # Authentication handlers
421
  auth_components['login_btn'].click(
422
- fn=self._handle_login,
423
  inputs=[
424
- auth_components['token_input'],
425
  user_state,
426
  token_state
427
  ],
@@ -429,7 +403,8 @@ class GradioInterface:
429
  auth_components['auth_status'],
430
  user_state,
431
  token_state,
432
- main_tabs
 
433
  ]
434
  )
435
 
@@ -591,6 +566,133 @@ class GradioInterface:
591
  outputs=[main_components['status_info']]
592
  )
593
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
  def _handle_login(self, token: str, current_user: Optional[str], current_token: Optional[str]) -> Tuple:
595
  """Handle user login with HuggingFace authentication."""
596
  if not token or not token.strip():
@@ -598,7 +700,8 @@ class GradioInterface:
598
  '<div class="error">Please enter a valid HuggingFace token</div>',
599
  current_user,
600
  current_token,
601
- gr.update(selected="auth_tab") # Stay on auth tab
 
602
  )
603
 
604
  # Authenticate user using official HuggingFace Hub methods
@@ -619,7 +722,8 @@ class GradioInterface:
619
  status_msg,
620
  auth_result.user.id,
621
  token.strip(),
622
- gr.update(selected="main_tab") # Go to main tab
 
623
  )
624
  else:
625
  # New user, go to egg selection
@@ -629,7 +733,8 @@ class GradioInterface:
629
  status_msg,
630
  auth_result.user.id,
631
  token.strip(),
632
- gr.update(selected="egg_tab") # Go to egg tab
 
633
  )
634
  else:
635
  error_msg = f'<div class="error">Login failed: {auth_result.error_message}</div>'
@@ -637,7 +742,8 @@ class GradioInterface:
637
  error_msg,
638
  current_user,
639
  current_token,
640
- gr.update(selected="auth_tab") # Stay on auth tab
 
641
  )
642
 
643
  def _handle_egg_selection(self, egg_type: EggType, user_id: Optional[str]) -> Tuple:
 
65
  user_state = gr.State(None)
66
  token_state = gr.State(None)
67
 
68
+ # Authentication Interface (visible by default)
69
+ with gr.Column(visible=True) as auth_container:
70
+ auth_components = self._create_authentication_tab()
71
+
72
+ # Main interface tabs (hidden until login)
73
+ with gr.Tabs(selected="egg_tab", visible=False) as main_tabs:
74
 
75
  # Egg Selection Tab
76
  with gr.Tab("Choose Your Egg", id="egg_tab") as egg_tab:
 
83
  # Event handlers
84
  self._setup_event_handlers(
85
  auth_components, egg_components, main_components,
86
+ user_state, token_state, main_tabs, auth_container
87
  )
88
 
89
  self.app = interface
 
104
  with gr.Column(scale=1):
105
  pass # Empty column for centering
106
 
107
+ with gr.Column(scale=2, elem_classes=["auth-form"]):
108
+ login_btn = gr.LoginButton(
109
+ value="Sign in with HuggingFace",
110
+ size="lg",
 
 
 
 
 
 
 
 
 
111
  elem_classes=["login-btn"]
112
  )
113
 
 
119
  with gr.Column(scale=1):
120
  pass # Empty column for centering
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
  return {
 
124
  'login_btn': login_btn,
125
  'auth_status': auth_status
126
  }
 
389
 
390
  def _setup_event_handlers(self, auth_components: Dict, egg_components: Dict,
391
  main_components: Dict, user_state: gr.State,
392
+ token_state: gr.State, main_tabs: gr.Tabs, auth_container: gr.Column):
393
  """Set up event handlers for all interface components."""
394
 
395
  # Authentication handlers
396
  auth_components['login_btn'].click(
397
+ fn=self._handle_oauth_login,
398
  inputs=[
 
399
  user_state,
400
  token_state
401
  ],
 
403
  auth_components['auth_status'],
404
  user_state,
405
  token_state,
406
+ main_tabs,
407
+ auth_container
408
  ]
409
  )
410
 
 
566
  outputs=[main_components['status_info']]
567
  )
568
 
569
+ def _handle_oauth_login(self, current_user: Optional[str], current_token: Optional[str]) -> Tuple:
570
+ """Handle OAuth login with HuggingFace."""
571
+ try:
572
+ # Check if user is authenticated via OAuth
573
+ request = gr.request()
574
+ if hasattr(request, 'user') and request.user:
575
+ # User is authenticated via OAuth
576
+ user_info = request.user
577
+
578
+ # Extract username from OAuth info
579
+ username = user_info.get('preferred_username', user_info.get('name', 'Unknown'))
580
+ user_id = user_info.get('sub', username)
581
+
582
+ # Create or update user in database
583
+ auth_result = self._create_oauth_user(user_id, username, user_info)
584
+
585
+ if auth_result and auth_result.status == AuthStatus.SUCCESS:
586
+ self.current_user_id = auth_result.user.id
587
+
588
+ # Check if user has existing DigiPal
589
+ existing_pet = self.digipal_core.load_existing_pet(auth_result.user.id)
590
+
591
+ if existing_pet:
592
+ # User has existing pet, go to main interface
593
+ status_msg = f'<div class="success">Welcome back, {auth_result.user.username}!</div>'
594
+
595
+ return (
596
+ status_msg,
597
+ auth_result.user.id,
598
+ "oauth_authenticated",
599
+ gr.update(selected="main_tab", visible=True), # Show main tabs
600
+ gr.update(visible=False) # Hide auth container
601
+ )
602
+ else:
603
+ # New user, go to egg selection
604
+ status_msg = f'<div class="success">Welcome, {auth_result.user.username}! Choose your first egg.</div>'
605
+
606
+ return (
607
+ status_msg,
608
+ auth_result.user.id,
609
+ "oauth_authenticated",
610
+ gr.update(selected="egg_tab", visible=True), # Show tabs with egg selection
611
+ gr.update(visible=False) # Hide auth container
612
+ )
613
+ else:
614
+ return (
615
+ '<div class="error">Failed to create user account</div>',
616
+ current_user,
617
+ current_token,
618
+ gr.update(visible=False), # Keep main tabs hidden
619
+ gr.update(visible=True) # Keep auth container visible
620
+ )
621
+ else:
622
+ # User not authenticated, show login prompt
623
+ return (
624
+ '<div class="info">Please sign in with your HuggingFace account</div>',
625
+ current_user,
626
+ current_token,
627
+ gr.update(visible=False), # Keep main tabs hidden
628
+ gr.update(visible=True) # Keep auth container visible
629
+ )
630
+
631
+ except Exception as e:
632
+ logger.error(f"OAuth authentication error: {e}")
633
+ return (
634
+ '<div class="error">Authentication failed. Please try again.</div>',
635
+ current_user,
636
+ current_token,
637
+ gr.update(visible=False), # Keep main tabs hidden
638
+ gr.update(visible=True) # Keep auth container visible
639
+ )
640
+
641
+ def _create_oauth_user(self, user_id: str, username: str, oauth_info: Dict) -> Optional[AuthResult]:
642
+ """Create or update user from OAuth information."""
643
+ try:
644
+ from ..auth.models import User, AuthResult, AuthStatus
645
+ from datetime import datetime
646
+
647
+ # Extract user data from OAuth info
648
+ email = oauth_info.get('email')
649
+ full_name = oauth_info.get('name', username)
650
+ avatar_url = oauth_info.get('picture')
651
+
652
+ # Check if user exists
653
+ existing_user = self.auth_manager.get_user(user_id)
654
+ now = datetime.now()
655
+
656
+ if existing_user:
657
+ # Update existing user
658
+ existing_user.username = username
659
+ existing_user.last_login = now
660
+ user = existing_user
661
+ else:
662
+ # Create new user
663
+ user = User(
664
+ id=user_id,
665
+ username=username,
666
+ email=email,
667
+ full_name=full_name,
668
+ avatar_url=avatar_url,
669
+ created_at=now,
670
+ last_login=now
671
+ )
672
+
673
+ # Save to database
674
+ self.auth_manager.db.execute_update(
675
+ '''INSERT OR REPLACE INTO users
676
+ (id, username, huggingface_token, created_at, last_login)
677
+ VALUES (?, ?, ?, ?, ?)''',
678
+ (user.id, user.username, "oauth_authenticated",
679
+ user.created_at.isoformat(), user.last_login.isoformat())
680
+ )
681
+
682
+ # Create session
683
+ session = self.auth_manager.session_manager.create_session(user, "oauth_authenticated")
684
+
685
+ logger.info(f"OAuth user created/updated: {username}")
686
+ return AuthResult(
687
+ status=AuthStatus.SUCCESS,
688
+ user=user,
689
+ session=session
690
+ )
691
+
692
+ except Exception as e:
693
+ logger.error(f"Error creating OAuth user: {e}")
694
+ return None
695
+
696
  def _handle_login(self, token: str, current_user: Optional[str], current_token: Optional[str]) -> Tuple:
697
  """Handle user login with HuggingFace authentication."""
698
  if not token or not token.strip():
 
700
  '<div class="error">Please enter a valid HuggingFace token</div>',
701
  current_user,
702
  current_token,
703
+ gr.update(visible=False), # Keep main tabs hidden
704
+ gr.update(visible=True) # Keep auth container visible
705
  )
706
 
707
  # Authenticate user using official HuggingFace Hub methods
 
722
  status_msg,
723
  auth_result.user.id,
724
  token.strip(),
725
+ gr.update(selected="main_tab", visible=True), # Show main tabs
726
+ gr.update(visible=False) # Hide auth container
727
  )
728
  else:
729
  # New user, go to egg selection
 
733
  status_msg,
734
  auth_result.user.id,
735
  token.strip(),
736
+ gr.update(selected="egg_tab", visible=True), # Show tabs with egg selection
737
+ gr.update(visible=False) # Hide auth container
738
  )
739
  else:
740
  error_msg = f'<div class="error">Login failed: {auth_result.error_message}</div>'
 
742
  error_msg,
743
  current_user,
744
  current_token,
745
+ gr.update(visible=False), # Keep main tabs hidden
746
+ gr.update(visible=True) # Keep auth container visible
747
  )
748
 
749
  def _handle_egg_selection(self, egg_type: EggType, user_id: Optional[str]) -> Tuple: