Ashkan Taghipour (The University of Western Australia) Claude Opus 4.6 commited on
Commit
1185c9e
·
1 Parent(s): 7ef3f1b

Replace broken multiselect with single-select + Add/Clear buttons

Browse files

Gradio 6.5.1 multiselect dropdown is unreliable. Replaced with:
- Single-select dropdown to pick one line at a time
- "Add to party" button to add selected line to party list
- "Clear party" button to reset
- Visible "Party members" textbox showing current selections
This matches the backpack pattern used in Quest 2/4 which works reliably.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +27 -18
  2. ui/quest1.py +16 -7
app.py CHANGED
@@ -156,31 +156,40 @@ with demo:
156
  outputs=[C["q1_umap_plot"]],
157
  )
158
 
159
- # Party selection via dropdown
160
- def on_party_dropdown(selected_lines, state):
 
 
 
 
 
 
 
 
161
  if state is None:
162
  state = AppState()
163
- # Gradio 6.x multiselect may pass None, a single string, or a list
164
- if selected_lines is None:
165
- state.selected_party = []
166
- elif isinstance(selected_lines, str):
167
- state.selected_party = [selected_lines] if selected_lines else []
168
- else:
169
- state.selected_party = list(selected_lines)
170
- if state.selected_party:
171
- text = f"Selected {len(state.selected_party)} lines: " + ", ".join(state.selected_party[:10])
172
- if len(state.selected_party) > 10:
173
- text += f" ... +{len(state.selected_party) - 10} more"
174
- else:
175
- text = "None selected"
176
- return text, state
177
 
178
- C["q1_party_dropdown"].change(
179
- fn=on_party_dropdown,
180
  inputs=[C["q1_party_dropdown"], C["state"]],
181
  outputs=[C["q1_party_display"], C["state"]],
182
  )
183
 
 
 
 
 
 
 
184
  def on_compare_click(state):
185
  fig, _ = on_compare_party(state, DATA)
186
  return gr.Plot(value=fig, visible=True)
 
156
  outputs=[C["q1_umap_plot"]],
157
  )
158
 
159
+ # Party selection via add / clear buttons
160
+ def _party_display_text(party):
161
+ if party:
162
+ text = f"Selected {len(party)} lines: " + ", ".join(party[:10])
163
+ if len(party) > 10:
164
+ text += f" ... +{len(party) - 10} more"
165
+ return text
166
+ return "None selected"
167
+
168
+ def on_add_to_party(selected_line, state):
169
  if state is None:
170
  state = AppState()
171
+ if selected_line and selected_line not in state.selected_party:
172
+ state.selected_party.append(selected_line)
173
+ return _party_display_text(state.selected_party), state
174
+
175
+ def on_clear_party(state):
176
+ if state is None:
177
+ state = AppState()
178
+ state.selected_party = []
179
+ return "None selected", state
 
 
 
 
 
180
 
181
+ C["q1_add_party_btn"].click(
182
+ fn=on_add_to_party,
183
  inputs=[C["q1_party_dropdown"], C["state"]],
184
  outputs=[C["q1_party_display"], C["state"]],
185
  )
186
 
187
+ C["q1_clear_party_btn"].click(
188
+ fn=on_clear_party,
189
+ inputs=[C["state"]],
190
+ outputs=[C["q1_party_display"], C["state"]],
191
+ )
192
+
193
  def on_compare_click(state):
194
  fig, _ = on_compare_party(state, DATA)
195
  return gr.Plot(value=fig, visible=True)
ui/quest1.py CHANGED
@@ -166,8 +166,8 @@ def build_quest1(line_choices: list[str] | None = None):
166
  that ``layout.py`` can prefix them with ``q1_`` and ``app.py`` can
167
  wire callbacks without changes.
168
 
169
- Keys: tab, color_radio, umap_plot, party_dropdown, party_display,
170
- compare_btn, comparison_plot
171
  """
172
  with gr.Tab("Genetic Landscape", id="quest1") as tab:
173
 
@@ -221,11 +221,18 @@ def build_quest1(line_choices: list[str] | None = None):
221
  with gr.Row():
222
  party_dropdown = gr.Dropdown(
223
  choices=line_choices or [],
224
- label="Select lines to compare (party members)",
225
- multiselect=True,
226
  interactive=True,
227
  filterable=True,
228
- allow_custom_value=False,
 
 
 
 
 
 
 
 
229
  )
230
  compare_btn = gr.Button(
231
  "Compare",
@@ -233,11 +240,11 @@ def build_quest1(line_choices: list[str] | None = None):
233
  )
234
 
235
  party_display = gr.Textbox(
236
- label="Selected party",
237
  interactive=False,
238
  value="None selected",
239
  lines=1,
240
- visible=False,
241
  )
242
 
243
  comparison_plot = gr.Plot(label="Comparison", visible=False)
@@ -262,6 +269,8 @@ def build_quest1(line_choices: list[str] | None = None):
262
  "color_radio": color_radio,
263
  "umap_plot": umap_plot,
264
  "party_dropdown": party_dropdown,
 
 
265
  "party_display": party_display,
266
  "compare_btn": compare_btn,
267
  "comparison_plot": comparison_plot,
 
166
  that ``layout.py`` can prefix them with ``q1_`` and ``app.py`` can
167
  wire callbacks without changes.
168
 
169
+ Keys: tab, color_radio, umap_plot, party_dropdown, add_party_btn,
170
+ clear_party_btn, party_display, compare_btn, comparison_plot
171
  """
172
  with gr.Tab("Genetic Landscape", id="quest1") as tab:
173
 
 
221
  with gr.Row():
222
  party_dropdown = gr.Dropdown(
223
  choices=line_choices or [],
224
+ label="Pick a line to add",
 
225
  interactive=True,
226
  filterable=True,
227
+ )
228
+ add_party_btn = gr.Button(
229
+ "Add to party",
230
+ variant="secondary",
231
+ )
232
+ clear_party_btn = gr.Button(
233
+ "Clear party",
234
+ variant="stop",
235
+ size="sm",
236
  )
237
  compare_btn = gr.Button(
238
  "Compare",
 
240
  )
241
 
242
  party_display = gr.Textbox(
243
+ label="Party members",
244
  interactive=False,
245
  value="None selected",
246
  lines=1,
247
+ visible=True,
248
  )
249
 
250
  comparison_plot = gr.Plot(label="Comparison", visible=False)
 
269
  "color_radio": color_radio,
270
  "umap_plot": umap_plot,
271
  "party_dropdown": party_dropdown,
272
+ "add_party_btn": add_party_btn,
273
+ "clear_party_btn": clear_party_btn,
274
  "party_display": party_display,
275
  "compare_btn": compare_btn,
276
  "comparison_plot": comparison_plot,