From eb2db3c9b25512bb71705c419f7a210c42c817b5 Mon Sep 17 00:00:00 2001 From: XucroYuri Date: Tue, 7 Jul 2026 12:16:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(webui):=20dropdown=20list=20=E5=8C=85?= =?UTF-8?q?=E8=A3=85=E5=AF=BC=E8=87=B4=20on=5Fmodel=5Fname=5Fchange=20?= =?UTF-8?q?=E5=B4=A9=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 测试发现: app.load/refresh_models_btn 直接用 scan_model_names 的返回值 (裸 list) 作为 model_name_dropdown 的输出, Gradio 把整个 list 当成 dropdown 的 value 而非 choices, 级联触发 on_model_name_change 时 model_name 收到的是 list 而非 str, 导致 'Path / list' TypeError。 修复: - 新增 refresh_model_dropdown() 返回 gr.Dropdown(choices=..., value=...), app.load 和 refresh_models_btn 改用它 (scan_model_names 保留为纯数据函数) - 新增 _coerce_single() 对 4 个事件处理函数 (on_model_name_change / on_ref_sample_change / on_apply_sample / on_auto_select_weights) 的 dropdown 入参做类型防御, 即使收到 list/tuple 也能还原为 str 已用真实 proplus-hc-dev 训练数据端到端验证: 选模型 -> 40 个样本正确填充 (含【情绪】标签) -> 预览播放器加载音频, 全链路无报错。 --- GPT_SoVITS/inference_webui.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/GPT_SoVITS/inference_webui.py b/GPT_SoVITS/inference_webui.py index fecbc57f..52d2966d 100644 --- a/GPT_SoVITS/inference_webui.py +++ b/GPT_SoVITS/inference_webui.py @@ -1223,6 +1223,25 @@ def scan_model_names() -> list[str]: ) +def refresh_model_dropdown(): + """刷新模型下拉框:返回 gr.Dropdown 更新(choices=全部模型名, value=首个)。 + + 供 app.load 和「刷新模型列表」按钮使用。 + 注意:必须返回 gr.Dropdown(...) 而非裸 list——裸 list 会被 Gradio 当作 + dropdown 的 value(多选),导致级联的 on_model_name_change 收到 list 而非 str。 + """ + names = scan_model_names() + return gr.Dropdown(choices=names, value=names[0] if names else "") + + +def _coerce_single(value): + """把可能被 Gradio 包装成 list/tuple 的单值还原为 str。""" + if isinstance(value, (list, tuple)): + return str(value[0]) if value else "" + return str(value) if value is not None else "" + + + def _read_name2text_for_webui(logs_dir: Path) -> dict[str, dict[str, str]]: """读取 2-name2text.txt(WebUI 版,与 api_v2.py 的 _read_name2text 逻辑一致)。 @@ -1275,6 +1294,7 @@ def _read_emotion_map_for_webui(model_name: str) -> dict[str, str]: def on_model_name_change(model_name: str): """模型名变化时,加载该模型的训练样本列表到下拉框与预览播放器。""" + model_name = _coerce_single(model_name) if not model_name: return gr.Dropdown(choices=[], value=""), gr.Audio(value=None) from config import exp_root @@ -1303,6 +1323,8 @@ def on_model_name_change(model_name: str): def on_ref_sample_change(sample_label: str, model_name: str): """训练样本选择变化时,更新预览播放器与情绪/括注文本框。""" + sample_label = _coerce_single(sample_label) + model_name = _coerce_single(model_name) if not sample_label or not model_name: return gr.Audio(value=None), "" from config import exp_root @@ -1316,6 +1338,8 @@ def on_ref_sample_change(sample_label: str, model_name: str): def on_apply_sample(sample_label: str, model_name: str): """应用选中样本:写入参考音频路径、参考文本、情绪/括注。""" + sample_label = _coerce_single(sample_label) + model_name = _coerce_single(model_name) if not sample_label or not model_name: return None, "", gr.Dropdown(), "" from config import exp_root @@ -1364,6 +1388,7 @@ def on_auto_select_weights(model_name: str): GPT_dropdown.change / SoVITS_dropdown.change 在 value 变化时自动触发。 (change_sovits_weights 是生成器,手动消费会丢失对其它组件的更新,故不直接调用。) """ + model_name = _coerce_single(model_name) if not model_name: return gr.Dropdown(), gr.Dropdown() weights = _scan_model_weights_for_webui() @@ -1617,7 +1642,7 @@ with gr.Blocks(title="GPT-SoVITS WebUI", analytics_enabled=False, js=js, css=css # ===== 新增事件绑定:训练角色选择 / 样本浏览 ===== refresh_models_btn.click( - fn=scan_model_names, + fn=refresh_model_dropdown, inputs=[], outputs=[model_name_dropdown], ) @@ -1642,7 +1667,7 @@ with gr.Blocks(title="GPT-SoVITS WebUI", analytics_enabled=False, js=js, css=css outputs=[GPT_dropdown, SoVITS_dropdown], ) # 页面加载时自动扫描模型列表 - app.load(fn=scan_model_names, inputs=[], outputs=[model_name_dropdown]) + app.load(fn=refresh_model_dropdown, inputs=[], outputs=[model_name_dropdown]) # gr.Markdown(value=i18n("文本切分工具。太长的文本合成出来效果不一定好,所以太长建议先切。合成会根据文本的换行分开合成再拼起来。"))