bebechien commited on
Commit
a6b7ef6
·
verified ·
1 Parent(s): 2f8fe79
Files changed (1) hide show
  1. ui.py +54 -105
ui.py CHANGED
@@ -1,6 +1,5 @@
1
  import gradio as gr
2
  from typing import Optional, Tuple, Generator, List, Any
3
- from huggingface_hub import HfApi
4
  from config import AppConfig
5
  from engine import FunctionGemmaEngine
6
 
@@ -13,49 +12,22 @@ class UIController:
13
  """
14
 
15
  @staticmethod
16
- def init_session(profile: Optional[gr.OAuthProfile] = None, token: Optional[gr.OAuthToken] = None) -> Tuple[Any, ...]:
17
  config = AppConfig()
18
  new_engine = FunctionGemmaEngine(config)
19
  username = profile.username if profile else None
20
 
21
- # Fetch available namespaces (User + Orgs) if logged in
22
- namespaces = []
23
- default_owner = None
24
-
25
- if token:
26
- try:
27
- api = HfApi(token=token.token)
28
- user_info = api.whoami()
29
- # Add user's own namespace first
30
- namespaces.append(user_info['name'])
31
- # Add organizations
32
- if 'orgs' in user_info:
33
- namespaces.extend([org['name'] for org in user_info['orgs']])
34
-
35
- default_owner = namespaces[0] if namespaces else None
36
- except Exception as e:
37
- print(f"Error fetching namespaces: {e}")
38
- pass
39
-
40
  # Calculate initial interactivity state
41
- repo_update, org_interactivity, push_update, zip_update = UIController.update_hub_interactive(new_engine, username)
42
-
43
- # Combine interactivity with the fetched choices for the Org Dropdown
44
- final_org_update = gr.update(
45
- choices=namespaces,
46
- value=default_owner,
47
- interactive=org_interactivity['interactive']
48
- )
49
 
50
  return (
51
  new_engine,
52
  new_engine.get_tools_json(),
53
  new_engine.config.MODEL_NAME,
54
  f"Ready. (Session {new_engine.session_id})",
55
- repo_update, # Model Name Input
56
- final_org_update, # Owner Dropdown
57
- push_update, # Push Button
58
- zip_update, # Zip Button
59
  username
60
  )
61
 
@@ -104,29 +76,23 @@ class UIController:
104
  return gr.update(value=None, visible=False)
105
 
106
  @staticmethod
107
- def upload_model(engine: FunctionGemmaEngine, owner: str, model_name: str, oauth_token: Optional[gr.OAuthToken]) -> str:
108
  if oauth_token is None:
109
  return "❌ Error: You must log in (top right) to upload models."
110
- if not owner:
111
- return "❌ Error: Please select an Owner (User or Organization)."
112
- if not model_name:
113
- return "❌ Error: Please enter a model name."
114
-
115
- # Construct the full repo_id (e.g. "google/functiongemma-tuned")
116
- full_repo_id = f"{owner}/{model_name.strip()}"
117
 
118
  return engine.upload_model_to_hub(
119
- repo_name=full_repo_id,
120
  oauth_token=oauth_token.token,
121
  )
122
 
123
  @staticmethod
124
- def update_repo_preview(owner: str, model_name: str) -> str:
125
- if not owner:
126
- return "Target Repository: (Select Owner First)"
127
-
128
- clean_name = model_name.strip() if model_name else "..."
129
- return f"Target Repository: **`{owner}/{clean_name}`**"
130
 
131
  @staticmethod
132
  def update_hub_interactive(engine: Optional[FunctionGemmaEngine], username: Optional[str] = None):
@@ -134,10 +100,9 @@ class UIController:
134
  has_model_tuned = engine is not None and getattr(engine, 'has_model_tuned', False)
135
 
136
  return (
137
- gr.update(interactive=is_logged_in), # Model Name Input
138
- gr.update(interactive=is_logged_in), # Owner Dropdown
139
- gr.update(interactive=is_logged_in and has_model_tuned), # Push Button
140
- gr.update(interactive=has_model_tuned) # Zip Button
141
  )
142
 
143
  # --- View / Layout Layer ---
@@ -225,23 +190,17 @@ def _render_export_tab(engine_state, username_state):
225
 
226
  with gr.Column():
227
  gr.Markdown("#### Option B: Save to Hugging Face Hub")
228
- gr.Markdown("Publish your fine-tuned model to your personal Hugging Face account or an Organization.")
229
-
230
- with gr.Row():
231
- org_dropdown = gr.Dropdown(
232
- label="Owner", choices=[], interactive=False, scale=1
233
- )
234
- model_name_input = gr.Textbox(
235
- label="Model Name", value="functiongemma-270m-it-tuning-lab", placeholder="e.g., functiongemma-tuned", interactive=False, scale=2
236
- )
237
-
238
  push_to_hub_btn = gr.Button("Save to Hugging Face Hub", variant="secondary", interactive=False)
239
  repo_id_preview = gr.Markdown("Target Repository: (Waiting for input...)")
240
  upload_status = gr.Markdown("")
241
 
242
  return {
243
  "zip_controls": [zip_btn, download_file],
244
- "hub_controls": [org_dropdown, model_name_input, push_to_hub_btn, repo_id_preview, upload_status]
245
  }
246
 
247
  # --- Main Build Function ---
@@ -259,22 +218,19 @@ def build_interface() -> gr.Blocks:
259
  export_ui = _render_export_tab(engine_state, username_state)
260
 
261
  # Helpers for UI State
 
 
262
  run_btn, stop_btn, reload_btn, eval_btn = train_ui["buttons"]
263
  action_buttons = [reload_btn, run_btn, eval_btn]
264
 
265
- # Hub Controls
266
- org_dropdown = export_ui["hub_controls"][0]
267
- model_name_input = export_ui["hub_controls"][1]
268
- push_btn = export_ui["hub_controls"][2]
269
  zip_btn = export_ui["zip_controls"][0]
270
 
271
- # The list of Hub-related inputs that need updating
272
- hub_inputs = [model_name_input, org_dropdown, push_btn, zip_btn]
273
-
274
  def lock_ui():
275
  """Locks all buttons (including Zip/Push) during processing"""
276
  return [gr.update(interactive=False) for _ in action_buttons] + \
277
- [gr.update(interactive=False) for _ in hub_inputs]
278
 
279
  def unlock_ui():
280
  """Unlocks general action buttons only. Zip/Push are handled by update_hub_interactive"""
@@ -283,24 +239,23 @@ def build_interface() -> gr.Blocks:
283
  # --- Event Wiring ---
284
 
285
  # 1. Initialization
286
- demo.load(lock_ui, outputs=action_buttons + hub_inputs).then(
287
  fn=UIController.init_session,
288
- inputs=None, # Gradio automatically injects profile and token
289
  outputs=[
290
  engine_state,
291
  data_ui["tools_editor"],
292
  train_ui["model_input"],
293
  train_ui["outputs"][0], # log output
294
- model_name_input, # Text update
295
- org_dropdown, # Dropdown update (choices+val)
296
  push_btn,
297
- zip_btn,
298
  username_state
299
  ]
300
  ).then(
301
  fn=UIController.update_repo_preview,
302
- inputs=[org_dropdown, model_name_input],
303
- outputs=[export_ui["hub_controls"][3]]
304
  ).then(unlock_ui, outputs=action_buttons)
305
 
306
  # 2. Data Tab
@@ -320,14 +275,14 @@ def build_interface() -> gr.Blocks:
320
 
321
  # 3a. Training
322
  train_run_event = run_btn.click(
323
- fn=lambda: [
324
- gr.update(visible=False), # run_btn
325
- gr.update(interactive=False), # reload_btn
326
- gr.update(interactive=False) # eval_btn
327
- ] + [gr.update(interactive=False) for _ in hub_inputs] + [ # Unpack Hub Inputs list
328
- gr.update(visible=True) # stop_btn
329
- ],
330
- outputs=[run_btn, reload_btn, eval_btn, *hub_inputs, stop_btn]
331
  )
332
  train_run_event = train_run_event.then(
333
  fn=UIController.run_training,
@@ -345,7 +300,7 @@ def build_interface() -> gr.Blocks:
345
  # Final check determines if Zip/Push should unlock
346
  fn=UIController.update_hub_interactive,
347
  inputs=[engine_state, username_state],
348
- outputs=hub_inputs
349
  )
350
 
351
  # 3b. Evaluation
@@ -353,7 +308,7 @@ def build_interface() -> gr.Blocks:
353
  fn=lambda: (
354
  gr.update(interactive=False), # Lock Run
355
  gr.update(interactive=False), # Lock Reload
356
- gr.update(visible=False), # Hide self
357
  gr.update(visible=True) # Show Stop
358
  ),
359
  outputs=[run_btn, reload_btn, eval_btn, stop_btn]
@@ -380,47 +335,41 @@ def build_interface() -> gr.Blocks:
380
  queue=False
381
  )
382
 
383
- reload_btn.click(lock_ui, outputs=action_buttons + hub_inputs).then(
384
  fn=UIController.handle_reset,
385
  inputs=[engine_state, train_ui["model_input"]],
386
  outputs=[train_ui["outputs"][0]]
387
  ).then(unlock_ui, outputs=action_buttons).then(
388
  fn=UIController.update_hub_interactive,
389
  inputs=[engine_state, username_state],
390
- outputs=hub_inputs
391
  )
392
 
393
  # 4. Export Tab
394
- zip_btn.click(lock_ui, outputs=action_buttons + hub_inputs).then(
395
  fn=UIController.zip_model,
396
  inputs=[engine_state],
397
  outputs=[export_ui["zip_controls"][1]]
398
  ).then(unlock_ui, outputs=action_buttons).then(
399
  fn=UIController.update_hub_interactive,
400
  inputs=[engine_state, username_state],
401
- outputs=hub_inputs
402
  )
403
 
404
- # Update preview when dropdown or text changes
405
- org_dropdown.change(
406
- fn=UIController.update_repo_preview,
407
- inputs=[org_dropdown, model_name_input],
408
- outputs=[export_ui["hub_controls"][3]]
409
- )
410
- model_name_input.change(
411
  fn=UIController.update_repo_preview,
412
- inputs=[org_dropdown, model_name_input],
413
- outputs=[export_ui["hub_controls"][3]]
414
  )
415
 
416
- push_btn.click(lock_ui, outputs=action_buttons + hub_inputs).then(
417
  fn=UIController.upload_model,
418
- inputs=[engine_state, org_dropdown, model_name_input], # oauth_token injected
419
- outputs=[export_ui["hub_controls"][4]]
420
  ).then(unlock_ui, outputs=action_buttons).then(
421
  fn=UIController.update_hub_interactive,
422
  inputs=[engine_state, username_state],
423
- outputs=hub_inputs
424
  )
425
 
426
  return demo
 
1
  import gradio as gr
2
  from typing import Optional, Tuple, Generator, List, Any
 
3
  from config import AppConfig
4
  from engine import FunctionGemmaEngine
5
 
 
12
  """
13
 
14
  @staticmethod
15
+ def init_session(profile: Optional[gr.OAuthProfile] = None) -> Tuple[Any, ...]:
16
  config = AppConfig()
17
  new_engine = FunctionGemmaEngine(config)
18
  username = profile.username if profile else None
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  # Calculate initial interactivity state
21
+ repo_update, push_update, zip_update = UIController.update_hub_interactive(new_engine, username)
 
 
 
 
 
 
 
22
 
23
  return (
24
  new_engine,
25
  new_engine.get_tools_json(),
26
  new_engine.config.MODEL_NAME,
27
  f"Ready. (Session {new_engine.session_id})",
28
+ repo_update,
29
+ push_update,
30
+ zip_update,
 
31
  username
32
  )
33
 
 
76
  return gr.update(value=None, visible=False)
77
 
78
  @staticmethod
79
+ def upload_model(engine: FunctionGemmaEngine, repo_name: str, oauth_token: Optional[gr.OAuthToken]) -> str:
80
  if oauth_token is None:
81
  return "❌ Error: You must log in (top right) to upload models."
82
+ if not repo_name:
83
+ return "❌ Error: Please enter a repository name."
 
 
 
 
 
84
 
85
  return engine.upload_model_to_hub(
86
+ repo_name=repo_name,
87
  oauth_token=oauth_token.token,
88
  )
89
 
90
  @staticmethod
91
+ def update_repo_preview(username: Optional[str], repo_name: str) -> str:
92
+ if not username:
93
+ return "⚠️ Sign in to see the target repository path."
94
+ clean_repo = repo_name.strip() if repo_name else "..."
95
+ return f"Target Repository: **`{username}/{clean_repo}`**"
 
96
 
97
  @staticmethod
98
  def update_hub_interactive(engine: Optional[FunctionGemmaEngine], username: Optional[str] = None):
 
100
  has_model_tuned = engine is not None and getattr(engine, 'has_model_tuned', False)
101
 
102
  return (
103
+ gr.update(interactive=is_logged_in),
104
+ gr.update(interactive=is_logged_in and has_model_tuned),
105
+ gr.update(interactive=has_model_tuned)
 
106
  )
107
 
108
  # --- View / Layout Layer ---
 
190
 
191
  with gr.Column():
192
  gr.Markdown("#### Option B: Save to Hugging Face Hub")
193
+ gr.Markdown("Publish your fine-tuned model to your personal Hugging Face account.")
194
+ repo_name_input = gr.Textbox(
195
+ label="Target Repository Name", value="functiongemma-270m-it-tuning-lab", placeholder="e.g., functiongemma-270m-it-tuned", interactive=False
196
+ )
 
 
 
 
 
 
197
  push_to_hub_btn = gr.Button("Save to Hugging Face Hub", variant="secondary", interactive=False)
198
  repo_id_preview = gr.Markdown("Target Repository: (Waiting for input...)")
199
  upload_status = gr.Markdown("")
200
 
201
  return {
202
  "zip_controls": [zip_btn, download_file],
203
+ "hub_controls": [repo_name_input, push_to_hub_btn, repo_id_preview, upload_status]
204
  }
205
 
206
  # --- Main Build Function ---
 
218
  export_ui = _render_export_tab(engine_state, username_state)
219
 
220
  # Helpers for UI State
221
+ # 'action_buttons' now ONLY contains buttons that should always be enabled after a process
222
+ # Zip and Push buttons are excluded here because their state depends on has_model_tuned
223
  run_btn, stop_btn, reload_btn, eval_btn = train_ui["buttons"]
224
  action_buttons = [reload_btn, run_btn, eval_btn]
225
 
226
+ repo_input = export_ui["hub_controls"][0]
227
+ push_btn = export_ui["hub_controls"][1]
 
 
228
  zip_btn = export_ui["zip_controls"][0]
229
 
 
 
 
230
  def lock_ui():
231
  """Locks all buttons (including Zip/Push) during processing"""
232
  return [gr.update(interactive=False) for _ in action_buttons] + \
233
+ [gr.update(interactive=False), gr.update(interactive=False)]
234
 
235
  def unlock_ui():
236
  """Unlocks general action buttons only. Zip/Push are handled by update_hub_interactive"""
 
239
  # --- Event Wiring ---
240
 
241
  # 1. Initialization
242
+ demo.load(lock_ui, outputs=action_buttons + [push_btn, zip_btn]).then(
243
  fn=UIController.init_session,
244
+ inputs=None,
245
  outputs=[
246
  engine_state,
247
  data_ui["tools_editor"],
248
  train_ui["model_input"],
249
  train_ui["outputs"][0], # log output
250
+ repo_input,
 
251
  push_btn,
252
+ zip_btn, # Update Zip state based on initial engine state
253
  username_state
254
  ]
255
  ).then(
256
  fn=UIController.update_repo_preview,
257
+ inputs=[username_state, repo_input],
258
+ outputs=[export_ui["hub_controls"][2]]
259
  ).then(unlock_ui, outputs=action_buttons)
260
 
261
  # 2. Data Tab
 
275
 
276
  # 3a. Training
277
  train_run_event = run_btn.click(
278
+ fn=lambda: (
279
+ gr.update(visible=False),
280
+ gr.update(interactive=False), # Lock Reload
281
+ gr.update(interactive=False), # Lock Eval
282
+ gr.update(interactive=False), # Lock Zip
283
+ gr.update(visible=True) # Show Stop
284
+ ),
285
+ outputs=[run_btn, reload_btn, eval_btn, zip_btn, stop_btn]
286
  )
287
  train_run_event = train_run_event.then(
288
  fn=UIController.run_training,
 
300
  # Final check determines if Zip/Push should unlock
301
  fn=UIController.update_hub_interactive,
302
  inputs=[engine_state, username_state],
303
+ outputs=[repo_input, push_btn, zip_btn]
304
  )
305
 
306
  # 3b. Evaluation
 
308
  fn=lambda: (
309
  gr.update(interactive=False), # Lock Run
310
  gr.update(interactive=False), # Lock Reload
311
+ gr.update(visible=False), # Hide self (optional, or lock)
312
  gr.update(visible=True) # Show Stop
313
  ),
314
  outputs=[run_btn, reload_btn, eval_btn, stop_btn]
 
335
  queue=False
336
  )
337
 
338
+ reload_btn.click(lock_ui, outputs=action_buttons + [push_btn, zip_btn]).then(
339
  fn=UIController.handle_reset,
340
  inputs=[engine_state, train_ui["model_input"]],
341
  outputs=[train_ui["outputs"][0]]
342
  ).then(unlock_ui, outputs=action_buttons).then(
343
  fn=UIController.update_hub_interactive,
344
  inputs=[engine_state, username_state],
345
+ outputs=[repo_input, push_btn, zip_btn]
346
  )
347
 
348
  # 4. Export Tab
349
+ zip_btn.click(lock_ui, outputs=action_buttons + [push_btn, zip_btn]).then(
350
  fn=UIController.zip_model,
351
  inputs=[engine_state],
352
  outputs=[export_ui["zip_controls"][1]]
353
  ).then(unlock_ui, outputs=action_buttons).then(
354
  fn=UIController.update_hub_interactive,
355
  inputs=[engine_state, username_state],
356
+ outputs=[repo_input, push_btn, zip_btn]
357
  )
358
 
359
+ repo_input.change(
 
 
 
 
 
 
360
  fn=UIController.update_repo_preview,
361
+ inputs=[username_state, repo_input],
362
+ outputs=[export_ui["hub_controls"][2]]
363
  )
364
 
365
+ push_btn.click(lock_ui, outputs=action_buttons + [push_btn, zip_btn]).then(
366
  fn=UIController.upload_model,
367
+ inputs=[engine_state, repo_input],
368
+ outputs=[export_ui["hub_controls"][3]]
369
  ).then(unlock_ui, outputs=action_buttons).then(
370
  fn=UIController.update_hub_interactive,
371
  inputs=[engine_state, username_state],
372
+ outputs=[repo_input, push_btn, zip_btn]
373
  )
374
 
375
  return demo