Spaces:
Running
Running
revert
Browse files
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
|
| 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,
|
| 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,
|
| 56 |
-
|
| 57 |
-
|
| 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,
|
| 108 |
if oauth_token is None:
|
| 109 |
return "❌ Error: You must log in (top right) to upload models."
|
| 110 |
-
if not
|
| 111 |
-
return "❌ Error: Please
|
| 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=
|
| 120 |
oauth_token=oauth_token.token,
|
| 121 |
)
|
| 122 |
|
| 123 |
@staticmethod
|
| 124 |
-
def update_repo_preview(
|
| 125 |
-
if not
|
| 126 |
-
return "
|
| 127 |
-
|
| 128 |
-
|
| 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),
|
| 138 |
-
gr.update(interactive=is_logged_in),
|
| 139 |
-
gr.update(interactive=
|
| 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
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 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": [
|
| 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 |
-
|
| 266 |
-
|
| 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)
|
| 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 +
|
| 287 |
fn=UIController.init_session,
|
| 288 |
-
inputs=None,
|
| 289 |
outputs=[
|
| 290 |
engine_state,
|
| 291 |
data_ui["tools_editor"],
|
| 292 |
train_ui["model_input"],
|
| 293 |
train_ui["outputs"][0], # log output
|
| 294 |
-
|
| 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=[
|
| 303 |
-
outputs=[export_ui["hub_controls"][
|
| 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),
|
| 325 |
-
gr.update(interactive=False),
|
| 326 |
-
gr.update(interactive=False)
|
| 327 |
-
|
| 328 |
-
gr.update(visible=True)
|
| 329 |
-
|
| 330 |
-
outputs=[run_btn, reload_btn, eval_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=
|
| 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 +
|
| 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=
|
| 391 |
)
|
| 392 |
|
| 393 |
# 4. Export Tab
|
| 394 |
-
zip_btn.click(lock_ui, outputs=action_buttons +
|
| 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=
|
| 402 |
)
|
| 403 |
|
| 404 |
-
|
| 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=[
|
| 413 |
-
outputs=[export_ui["hub_controls"][
|
| 414 |
)
|
| 415 |
|
| 416 |
-
push_btn.click(lock_ui, outputs=action_buttons +
|
| 417 |
fn=UIController.upload_model,
|
| 418 |
-
inputs=[engine_state,
|
| 419 |
-
outputs=[export_ui["hub_controls"][
|
| 420 |
).then(unlock_ui, outputs=action_buttons).then(
|
| 421 |
fn=UIController.update_hub_interactive,
|
| 422 |
inputs=[engine_state, username_state],
|
| 423 |
-
outputs=
|
| 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
|