bebechien commited on
Commit
0a2e0b5
·
verified ·
1 Parent(s): 1cc09c6

Refactor ui.py

Browse files
Files changed (2) hide show
  1. README.md +2 -0
  2. ui.py +238 -222
README.md CHANGED
@@ -8,6 +8,8 @@ sdk_version: 6.0.1
8
  app_file: app.py
9
  pinned: false
10
  hf_oauth: true
 
 
11
  license: apache-2.0
12
  ---
13
 
 
8
  app_file: app.py
9
  pinned: false
10
  hf_oauth: true
11
+ hf_oauth_scopes:
12
+ - write-repos
13
  license: apache-2.0
14
  ---
15
 
ui.py CHANGED
@@ -1,52 +1,74 @@
1
  import gradio as gr
 
2
  from config import AppConfig
3
  from engine import FunctionGemmaEngine
4
- from typing import Optional
5
 
6
- def build_interface() -> gr.Blocks:
7
-
8
- # --- State Management Wrappers ---
9
-
10
- def init_session(profile: Optional[gr.OAuthProfile] = None):
 
 
 
 
 
11
  config = AppConfig()
12
  new_engine = FunctionGemmaEngine(config)
13
-
14
  username = profile.username if profile else None
15
- repo_update, push_update = update_hub_interactive(new_engine, username)
 
 
 
16
 
17
  return (
18
  new_engine,
19
  new_engine.get_tools_json(),
20
  new_engine.config.MODEL_NAME,
21
  f"Ready. (Session {new_engine.session_id})",
22
- repo_update, push_update, username
 
 
 
23
  )
24
 
25
- def run_training_wrapper(engine, epochs, lr, test_size, shuffle, model_name):
 
 
 
 
 
 
26
  engine.config.MODEL_NAME = model_name.strip()
27
  yield from engine.run_training_pipeline(epochs, lr, test_size, shuffle)
28
 
29
- def handle_reset(engine, model_name):
 
30
  engine.config.MODEL_NAME = model_name.strip()
31
  return engine.refresh_model()
32
 
33
- def update_tools_wrapper(engine, json_val):
 
34
  return engine.update_tools(json_val)
35
 
36
- def import_file_wrapper(engine, file_obj):
 
37
  return engine.load_csv(file_obj)
38
 
39
- def stop_wrapper(engine):
 
40
  engine.trigger_stop()
41
  return "Stopping..."
42
 
43
- def zip_wrapper(engine):
 
44
  path = engine.get_zip_path()
45
  if path:
46
  return gr.update(value=path, visible=True)
47
  return gr.update(value=None, visible=False)
48
 
49
- def upload_wrapper(engine, repo_name, oauth_token: gr.OAuthToken | None):
 
50
  if oauth_token is None:
51
  return "❌ Error: You must log in (top right) to upload models."
52
  if not repo_name:
@@ -57,258 +79,252 @@ def build_interface() -> gr.Blocks:
57
  oauth_token=oauth_token.token,
58
  )
59
 
60
- def update_repo_preview(username, repo_name):
61
- """Updates the markdown preview to show 'username/repo_name'."""
62
  if not username:
63
  return "⚠️ Sign in to see the target repository path."
64
-
65
  clean_repo = repo_name.strip() if repo_name else "..."
66
  return f"Target Repository: **`{username}/{clean_repo}`**"
67
 
68
- def update_hub_interactive(engine, username: Optional[str] = None):
 
69
  is_logged_in = username is not None
70
- has_model_tuned = engine is not None and engine.has_model_tuned
71
 
72
- return gr.update(interactive=is_logged_in), gr.update(interactive=is_logged_in and has_model_tuned)
 
 
 
 
 
 
73
 
74
- # --- UI Layout ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  with gr.Blocks(title="FunctionGemma Modkit") as demo:
76
  engine_state = gr.State()
77
  username_state = gr.State()
78
 
79
- with gr.Column():
80
- gr.Markdown("# 🤖 FunctionGemma Modkit: Fine-Tuning")
81
- gr.Markdown("Fine-tune FunctionGemma to understand your custom functions.<br>See [README](https://huggingface.co/spaces/google/functiongemma-modkit/blob/main/README.md) for more details.")
82
- gr.Markdown("(Optional) Sign in to Hugging Face if you plan to push your fine-tuned model to the Hub later (3. Export).")
83
- with gr.Row():
84
- gr.LoginButton(value="Sign in with Hugging Face")
85
- with gr.Column(scale=3):
86
- gr.Markdown("")
87
 
88
  with gr.Tabs():
89
-
90
- # --- TAB 1: PREPARING DATASET ---
91
- with gr.TabItem("1. Preparing Dataset"):
92
- gr.Markdown("### 🛠️ Tool Schema & Data Import")
93
-
94
- with gr.Row():
95
- with gr.Column(scale=1):
96
- gr.Markdown("**Step 1: Define Functions**<br>Edit the JSON schema below to define the tools the model should learn.")
97
- tools_editor = gr.Code(
98
- language="json",
99
- label="Tool Definitions (JSON Schema)",
100
- lines=15
101
- )
102
- update_tools_btn = gr.Button("💾 Update Tool Schema")
103
- tools_status = gr.Markdown("")
104
-
105
- with gr.Column(scale=1):
106
- gr.Markdown("**Step 2: Upload Data (Optional)**<br>To train on your own data, upload a CSV file to replace the [default dataset](https://huggingface.co/datasets/bebechien/SimpleToolCalling).")
107
- gr.Markdown("**Example CSV Row:** No header required.<br>Format: `[User Prompt, Tool Name, Tool Args JSON]`\n```csv\n\"What is the weather in London?\", \"get_weather\", \"{\"\"location\"\": \"\"London, UK\"\"}\"\n```")
108
- import_file = gr.File(
109
- label="Upload Dataset (.csv)",
110
- file_types=[".csv"],
111
- height=100
112
- )
113
- import_status = gr.Markdown("")
114
-
115
- # --- TAB 2: TRAINING ---
116
- with gr.TabItem("2. Training"):
117
- gr.Markdown("### 🚀 Fine-Tuning Configuration")
118
-
119
- with gr.Group():
120
- gr.Markdown("**Hyperparameters**")
121
- with gr.Row():
122
- default_models = AppConfig().AVAILABLE_MODELS
123
- param_model = gr.Dropdown(
124
- choices=default_models,
125
- allow_custom_value=True,
126
- label="Base Model",
127
- info="Select a preset OR type a custom Hugging Face model ID (e.g. 'google/functiongemma-270m-it')",
128
- interactive=True
129
- )
130
- param_epochs = gr.Slider(
131
- minimum=1, maximum=20, value=5, step=1,
132
- label="Epochs", info="Total training passes"
133
- )
134
- with gr.Row():
135
- param_lr = gr.Number(
136
- value=5e-5,
137
- label="Learning Rate",
138
- info="e.g. 5e-5"
139
- )
140
- param_test_size = gr.Slider(
141
- minimum=0.1, maximum=0.9, value=0.2, step=0.05,
142
- label="Test Split", info="Validation ratio (0.2 = 20%)"
143
- )
144
- param_shuffle = gr.Checkbox(
145
- value=True,
146
- label="Shuffle Data",
147
- info="Randomize before split"
148
- )
149
-
150
- with gr.Row():
151
- run_training_btn = gr.Button("🚀 Run Fine-Tuning", variant="primary", scale=1)
152
- stop_training_btn = gr.Button("🛑 Stop", variant="stop", visible=False, scale=1)
153
- clear_reload_btn = gr.Button("🔄 Reload Model & Reset Data", variant="secondary", scale=1)
154
-
155
- with gr.Row():
156
- output_display = gr.Textbox(
157
- lines=20,
158
- label="Logs & Results",
159
- value="Initializing...",
160
- interactive=False,
161
- autoscroll=True
162
- )
163
- loss_plot = gr.Plot(label="Training Metrics")
164
-
165
- # --- TAB 3: EXPORT ---
166
- with gr.TabItem("3. Export"):
167
- gr.Markdown("### 📦 Export Trained Model")
168
-
169
- with gr.Row():
170
- with gr.Column():
171
- gr.Markdown("#### Option A: Download ZIP")
172
- gr.Markdown("Download the model weights locally.")
173
- zip_btn = gr.Button("⬇️ Prepare Model ZIP", variant="secondary", interactive=False)
174
- download_file = gr.File(label="Download Archive", interactive=False)
175
-
176
- with gr.Column():
177
- gr.Markdown("#### Option B: Save to Hugging Face Hub")
178
- gr.Markdown("Publish your fine-tuned model to your personal Hugging Face account.")
179
-
180
- repo_name_input = gr.Textbox(
181
- label="TargetRepository Name",
182
- value="my-functiongemma-v1",
183
- placeholder="e.g., my-functiongemma-v1",
184
- interactive=False
185
- )
186
- push_to_hub_btn = gr.Button("Save to Hugging Face Hub", variant="secondary", interactive=False)
187
- repo_id_preview = gr.Markdown("Target Repository: (Waiting for input...)")
188
-
189
- upload_status = gr.Markdown("")
190
-
191
- # --- EVENT WIRING ---
192
 
193
- action_buttons = [
194
- clear_reload_btn,
195
- run_training_btn,
196
- zip_btn
197
- ]
198
 
199
- def set_interactivity(interactive: bool):
200
- return [gr.update(interactive=interactive) for _ in action_buttons]
201
 
202
- demo.load(
203
- fn=lambda: set_interactivity(False), outputs=action_buttons
204
- ).then(
205
- fn=init_session,
206
  inputs=None,
207
- outputs=[engine_state, tools_editor, param_model, output_display, repo_name_input, push_to_hub_btn, username_state]
 
 
 
 
 
 
 
 
 
208
  ).then(
209
- fn=update_repo_preview,
210
- inputs=[username_state, repo_name_input],
211
- outputs=[repo_id_preview]
212
- ).then(
213
- fn=lambda: [gr.update(interactive=True)]*2, outputs=[clear_reload_btn, run_training_btn]
214
- )
215
 
216
- update_tools_btn.click(
217
- fn=update_tools_wrapper,
218
- inputs=[engine_state, tools_editor],
219
- outputs=[tools_status]
 
220
  )
221
 
222
- import_file.upload(
223
- fn=import_file_wrapper,
224
- inputs=[engine_state, import_file],
225
- outputs=[import_status]
226
  )
 
 
 
227
 
228
- run_training_btn.click(
229
  fn=lambda: (
230
  gr.update(visible=False),
231
- gr.update(interactive=False),
232
- gr.update(interactive=False),
233
- gr.update(visible=True)
234
  ),
235
- outputs=[run_training_btn, clear_reload_btn, zip_btn, stop_training_btn]
236
  ).then(
237
- fn=run_training_wrapper,
238
- inputs=[engine_state, param_epochs, param_lr, param_test_size, param_shuffle, param_model],
239
- outputs=[output_display, loss_plot],
240
  ).then(
241
  fn=lambda: (
242
  gr.update(visible=True),
243
  gr.update(interactive=True),
244
- gr.update(interactive=True),
245
  gr.update(visible=False)
246
  ),
247
- outputs=[run_training_btn, clear_reload_btn, zip_btn, stop_training_btn]
248
  ).then(
249
- fn=update_hub_interactive,
 
250
  inputs=[engine_state, username_state],
251
- outputs=[repo_name_input, push_to_hub_btn]
252
- )
253
-
254
- stop_training_btn.click(
255
- fn=stop_wrapper,
256
- inputs=[engine_state],
257
- outputs=None
258
  )
259
 
260
- clear_reload_btn.click(
261
- fn=lambda: set_interactivity(False), outputs=action_buttons
262
- ).then(
263
- fn=lambda: gr.update(interactive=False), outputs=push_to_hub_btn
264
- ).then(
265
- fn=handle_reset,
266
- inputs=[engine_state, param_model],
267
- outputs=[output_display]
268
- ).then(
269
- fn=lambda: [gr.update(interactive=True)]*2, outputs=[clear_reload_btn, run_training_btn]
270
- ).then(
271
- fn=update_hub_interactive,
272
  inputs=[engine_state, username_state],
273
- outputs=[repo_name_input, push_to_hub_btn]
274
  )
275
-
276
- zip_btn.click(
277
- fn=lambda: set_interactivity(False), outputs=action_buttons
278
- ).then(
279
- fn=lambda: gr.update(interactive=False), outputs=push_to_hub_btn
280
- ).then(
281
- fn=zip_wrapper,
282
  inputs=[engine_state],
283
- outputs=[download_file]
284
- ).then(
285
- fn=lambda: set_interactivity(True), outputs=action_buttons
286
- ).then(
287
- fn=update_hub_interactive,
288
  inputs=[engine_state, username_state],
289
- outputs=[repo_name_input, push_to_hub_btn]
290
  )
291
-
292
- repo_name_input.change(
293
- fn=update_repo_preview,
294
- inputs=[username_state, repo_name_input],
295
- outputs=[repo_id_preview]
296
  )
297
 
298
- push_to_hub_btn.click(
299
- fn=lambda: set_interactivity(False), outputs=action_buttons
300
- ).then(
301
- fn=lambda: gr.update(interactive=False), outputs=push_to_hub_btn
302
- ).then(
303
- fn=upload_wrapper,
304
- inputs=[engine_state, repo_name_input],
305
- outputs=[upload_status]
306
- ).then(
307
- fn=lambda: set_interactivity(True), outputs=action_buttons
308
- ).then(
309
- fn=update_hub_interactive,
310
  inputs=[engine_state, username_state],
311
- outputs=[repo_name_input, push_to_hub_btn]
312
  )
313
 
314
  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
 
6
+ # --- Controller / Logic Layer ---
7
+
8
+ class UIController:
9
+ """
10
+ Handles the business logic and interaction with the Engine.
11
+ Stateless methods that operate on the passed Engine state.
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
+ username="bebechien"
20
+
21
+ # Calculate initial interactivity state
22
+ repo_update, push_update, zip_update = UIController.update_hub_interactive(new_engine, username)
23
 
24
  return (
25
  new_engine,
26
  new_engine.get_tools_json(),
27
  new_engine.config.MODEL_NAME,
28
  f"Ready. (Session {new_engine.session_id})",
29
+ repo_update,
30
+ push_update,
31
+ zip_update,
32
+ username
33
  )
34
 
35
+ @staticmethod
36
+ def run_training(engine: FunctionGemmaEngine, epochs: int, lr: float,
37
+ test_size: float, shuffle: bool, model_name: str) -> Generator:
38
+ if not engine:
39
+ yield "⚠️ Engine not initialized.", None
40
+ return
41
+
42
  engine.config.MODEL_NAME = model_name.strip()
43
  yield from engine.run_training_pipeline(epochs, lr, test_size, shuffle)
44
 
45
+ @staticmethod
46
+ def handle_reset(engine: FunctionGemmaEngine, model_name: str) -> str:
47
  engine.config.MODEL_NAME = model_name.strip()
48
  return engine.refresh_model()
49
 
50
+ @staticmethod
51
+ def update_tools(engine: FunctionGemmaEngine, json_val: str) -> str:
52
  return engine.update_tools(json_val)
53
 
54
+ @staticmethod
55
+ def import_file(engine: FunctionGemmaEngine, file_obj: Any) -> str:
56
  return engine.load_csv(file_obj)
57
 
58
+ @staticmethod
59
+ def stop_process(engine: FunctionGemmaEngine) -> str:
60
  engine.trigger_stop()
61
  return "Stopping..."
62
 
63
+ @staticmethod
64
+ def zip_model(engine: FunctionGemmaEngine) -> Any:
65
  path = engine.get_zip_path()
66
  if path:
67
  return gr.update(value=path, visible=True)
68
  return gr.update(value=None, visible=False)
69
 
70
+ @staticmethod
71
+ def upload_model(engine: FunctionGemmaEngine, repo_name: str, oauth_token: Optional[gr.OAuthToken]) -> str:
72
  if oauth_token is None:
73
  return "❌ Error: You must log in (top right) to upload models."
74
  if not repo_name:
 
79
  oauth_token=oauth_token.token,
80
  )
81
 
82
+ @staticmethod
83
+ def update_repo_preview(username: Optional[str], repo_name: str) -> str:
84
  if not username:
85
  return "⚠️ Sign in to see the target repository path."
 
86
  clean_repo = repo_name.strip() if repo_name else "..."
87
  return f"Target Repository: **`{username}/{clean_repo}`**"
88
 
89
+ @staticmethod
90
+ def update_hub_interactive(engine: Optional[FunctionGemmaEngine], username: Optional[str] = None):
91
  is_logged_in = username is not None
92
+ has_model_tuned = engine is not None and getattr(engine, 'has_model_tuned', False)
93
 
94
+ return (
95
+ gr.update(interactive=is_logged_in),
96
+ gr.update(interactive=is_logged_in and has_model_tuned),
97
+ gr.update(interactive=has_model_tuned)
98
+ )
99
+
100
+ # --- View / Layout Layer ---
101
 
102
+ def _render_header():
103
+ with gr.Column():
104
+ gr.Markdown("# 🤖 FunctionGemma Modkit: Fine-Tuning")
105
+ gr.Markdown("Fine-tune FunctionGemma to understand your custom functions.<br>"
106
+ "See [README](https://huggingface.co/spaces/google/functiongemma-modkit/blob/main/README.md) for more details.")
107
+ gr.Markdown("(Optional) Sign in to Hugging Face if you plan to push your fine-tuned model to the Hub later (3. Export).")
108
+ with gr.Row():
109
+ gr.LoginButton(value="Sign in with Hugging Face")
110
+ with gr.Column(scale=3):
111
+ gr.Markdown("")
112
+
113
+ def _render_dataset_tab(engine_state):
114
+ with gr.TabItem("1. Preparing Dataset"):
115
+ gr.Markdown("### 🛠️ Tool Schema & Data Import")
116
+ with gr.Row():
117
+ with gr.Column(scale=1):
118
+ gr.Markdown("**Step 1: Define Functions**<br>Edit the JSON schema below to define the tools the model should learn.")
119
+ tools_editor = gr.Code(language="json", label="Tool Definitions (JSON Schema)", lines=15)
120
+ update_tools_btn = gr.Button("💾 Update Tool Schema")
121
+ tools_status = gr.Markdown("")
122
+
123
+ with gr.Column(scale=1):
124
+ gr.Markdown("**Step 2: Upload Data (Optional)**<br>To train on your own data, upload a CSV file to replace the [default dataset](https://huggingface.co/datasets/bebechien/SimpleToolCalling).")
125
+ gr.Markdown("**Example CSV Row:** No header required.<br>Format: `[User Prompt, Tool Name, Tool Args JSON]`\n```csv\n\"What is the weather in London?\", \"get_weather\", \"{\"\"location\"\": \"\"London, UK\"\"}\"\n```")
126
+ import_file = gr.File(label="Upload Dataset (.csv)", file_types=[".csv"], height=100)
127
+ import_status = gr.Markdown("")
128
+
129
+ # Return controls needed for wiring
130
+ return {
131
+ "tools_editor": tools_editor,
132
+ "update_tools_btn": update_tools_btn,
133
+ "tools_status": tools_status,
134
+ "import_file": import_file,
135
+ "import_status": import_status
136
+ }
137
+
138
+ def _render_training_tab(engine_state):
139
+ with gr.TabItem("2. Training"):
140
+ gr.Markdown("### 🚀 Fine-Tuning Configuration")
141
+ with gr.Group():
142
+ gr.Markdown("**Hyperparameters**")
143
+ with gr.Row():
144
+ default_models = AppConfig().AVAILABLE_MODELS
145
+ param_model = gr.Dropdown(
146
+ choices=default_models, allow_custom_value=True, label="Base Model", info="Select a preset OR type a custom Hugging Face model ID (e.g. 'google/functiongemma-270m-it')", interactive=True
147
+ )
148
+ param_epochs = gr.Slider(1, 20, value=5, step=1, label="Epochs", info="Total training passes")
149
+ with gr.Row():
150
+ param_lr = gr.Number(value=5e-5, label="Learning Rate", info="e.g. 5e-5")
151
+ param_test_size = gr.Slider(0.1, 0.9, value=0.2, step=0.05, label="Test Split", info="Validation ratio (0.2 = 20%)")
152
+ param_shuffle = gr.Checkbox(value=True, label="Shuffle Data", info="Randomize before split")
153
+
154
+ with gr.Row():
155
+ run_training_btn = gr.Button("🚀 Run Fine-Tuning", variant="primary", scale=1)
156
+ stop_training_btn = gr.Button("🛑 Stop", variant="stop", visible=False, scale=1)
157
+ clear_reload_btn = gr.Button("🔄 Reload Model & Reset Data", variant="secondary", scale=1)
158
+
159
+ with gr.Row():
160
+ output_display = gr.Textbox(lines=20, label="Logs", value="Initializing...", interactive=False, autoscroll=True)
161
+ loss_plot = gr.Plot(label="Training Metrics")
162
+
163
+ return {
164
+ "params": [param_epochs, param_lr, param_test_size, param_shuffle, param_model],
165
+ "buttons": [run_training_btn, stop_training_btn, clear_reload_btn],
166
+ "outputs": [output_display, loss_plot],
167
+ "model_input": param_model # specifically needed for initialization
168
+ }
169
+
170
+ def _render_export_tab(engine_state, username_state):
171
+ with gr.TabItem("3. Export"):
172
+ gr.Markdown("### 📦 Export Trained Model")
173
+ with gr.Row():
174
+ with gr.Column():
175
+ gr.Markdown("#### Option A: Download ZIP")
176
+ gr.Markdown("Download the model weights locally.")
177
+ zip_btn = gr.Button("⬇️ Prepare Model ZIP", variant="secondary", interactive=False)
178
+ download_file = gr.File(label="Download Archive", interactive=False)
179
+
180
+ with gr.Column():
181
+ gr.Markdown("#### Option B: Save to Hugging Face Hub")
182
+ gr.Markdown("Publish your fine-tuned model to your personal Hugging Face account.")
183
+ repo_name_input = gr.Textbox(
184
+ label="Target Repository Name", value="my-functiongemma-v1", placeholder="e.g., my-functiongemma-v1", interactive=False
185
+ )
186
+ push_to_hub_btn = gr.Button("Save to Hugging Face Hub", variant="secondary", interactive=False)
187
+ repo_id_preview = gr.Markdown("Target Repository: (Waiting for input...)")
188
+ upload_status = gr.Markdown("")
189
+
190
+ return {
191
+ "zip_controls": [zip_btn, download_file],
192
+ "hub_controls": [repo_name_input, push_to_hub_btn, repo_id_preview, upload_status]
193
+ }
194
+
195
+ # --- Main Build Function ---
196
+
197
+ def build_interface() -> gr.Blocks:
198
  with gr.Blocks(title="FunctionGemma Modkit") as demo:
199
  engine_state = gr.State()
200
  username_state = gr.State()
201
 
202
+ _render_header()
 
 
 
 
 
 
 
203
 
204
  with gr.Tabs():
205
+ data_ui = _render_dataset_tab(engine_state)
206
+ train_ui = _render_training_tab(engine_state)
207
+ export_ui = _render_export_tab(engine_state, username_state)
208
+
209
+ # Helpers for UI State
210
+ # 'action_buttons' now ONLY contains buttons that should always be enabled after a process
211
+ # Zip and Push buttons are excluded here because their state depends on has_model_tuned
212
+ action_buttons = [train_ui["buttons"][2], train_ui["buttons"][0]] # [Reload, Run]
213
+
214
+ repo_input = export_ui["hub_controls"][0]
215
+ push_btn = export_ui["hub_controls"][1]
216
+ zip_btn = export_ui["zip_controls"][0]
217
+
218
+ def lock_ui():
219
+ """Locks all buttons (including Zip/Push) during processing"""
220
+ return [gr.update(interactive=False) for _ in action_buttons] + \
221
+ [gr.update(interactive=False), gr.update(interactive=False)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
+ def unlock_ui():
224
+ """Unlocks general action buttons only. Zip/Push are handled by update_hub_interactive"""
225
+ return [gr.update(interactive=True) for _ in action_buttons]
 
 
226
 
227
+ # --- Event Wiring ---
 
228
 
229
+ # 1. Initialization
230
+ demo.load(lock_ui, outputs=action_buttons + [push_btn, zip_btn]).then(
231
+ fn=UIController.init_session,
 
232
  inputs=None,
233
+ outputs=[
234
+ engine_state,
235
+ data_ui["tools_editor"],
236
+ train_ui["model_input"],
237
+ train_ui["outputs"][0], # log output
238
+ repo_input,
239
+ push_btn,
240
+ zip_btn, # Update Zip state based on initial engine state
241
+ username_state
242
+ ]
243
  ).then(
244
+ fn=UIController.update_repo_preview,
245
+ inputs=[username_state, repo_input],
246
+ outputs=[export_ui["hub_controls"][2]]
247
+ ).then(unlock_ui, outputs=action_buttons)
 
 
248
 
249
+ # 2. Data Tab
250
+ data_ui["update_tools_btn"].click(
251
+ fn=UIController.update_tools,
252
+ inputs=[engine_state, data_ui["tools_editor"]],
253
+ outputs=[data_ui["tools_status"]]
254
  )
255
 
256
+ data_ui["import_file"].upload(
257
+ fn=UIController.import_file,
258
+ inputs=[engine_state, data_ui["import_file"]],
259
+ outputs=[data_ui["import_status"]]
260
  )
261
+
262
+ # 3. Training Tab
263
+ run_btn, stop_btn, reload_btn = train_ui["buttons"]
264
 
265
+ run_btn.click(
266
  fn=lambda: (
267
  gr.update(visible=False),
268
+ gr.update(interactive=False), # Lock Reload
269
+ gr.update(interactive=False), # Lock Zip
270
+ gr.update(visible=True) # Show Stop
271
  ),
272
+ outputs=[run_btn, reload_btn, zip_btn, stop_btn]
273
  ).then(
274
+ fn=UIController.run_training,
275
+ inputs=[engine_state, *train_ui["params"]],
276
+ outputs=train_ui["outputs"],
277
  ).then(
278
  fn=lambda: (
279
  gr.update(visible=True),
280
  gr.update(interactive=True),
 
281
  gr.update(visible=False)
282
  ),
283
+ outputs=[run_btn, reload_btn, stop_btn]
284
  ).then(
285
+ # Final check determines if Zip/Push should unlock
286
+ fn=UIController.update_hub_interactive,
287
  inputs=[engine_state, username_state],
288
+ outputs=[repo_input, push_btn, zip_btn]
 
 
 
 
 
 
289
  )
290
 
291
+ stop_btn.click(fn=UIController.stop_process, inputs=[engine_state])
292
+
293
+ reload_btn.click(lock_ui, outputs=action_buttons + [push_btn, zip_btn]).then(
294
+ fn=UIController.handle_reset,
295
+ inputs=[engine_state, train_ui["model_input"]],
296
+ outputs=[train_ui["outputs"][0]]
297
+ ).then(unlock_ui, outputs=action_buttons).then(
298
+ fn=UIController.update_hub_interactive,
 
 
 
 
299
  inputs=[engine_state, username_state],
300
+ outputs=[repo_input, push_btn, zip_btn]
301
  )
302
+
303
+ # 4. Export Tab
304
+ zip_btn.click(lock_ui, outputs=action_buttons + [push_btn, zip_btn]).then(
305
+ fn=UIController.zip_model,
 
 
 
306
  inputs=[engine_state],
307
+ outputs=[export_ui["zip_controls"][1]]
308
+ ).then(unlock_ui, outputs=action_buttons).then(
309
+ fn=UIController.update_hub_interactive,
 
 
310
  inputs=[engine_state, username_state],
311
+ outputs=[repo_input, push_btn, zip_btn]
312
  )
313
+
314
+ repo_input.change(
315
+ fn=UIController.update_repo_preview,
316
+ inputs=[username_state, repo_input],
317
+ outputs=[export_ui["hub_controls"][2]]
318
  )
319
 
320
+ push_btn.click(lock_ui, outputs=action_buttons + [push_btn, zip_btn]).then(
321
+ fn=UIController.upload_model,
322
+ inputs=[engine_state, repo_input],
323
+ outputs=[export_ui["hub_controls"][3]]
324
+ ).then(unlock_ui, outputs=action_buttons).then(
325
+ fn=UIController.update_hub_interactive,
 
 
 
 
 
 
326
  inputs=[engine_state, username_state],
327
+ outputs=[repo_input, push_btn, zip_btn]
328
  )
329
 
330
  return demo