{"record":{"id":"65264ecd2c9ae0ef","repo":"harry0703/MoneyPrinterTurbo","slug":"selectbox-options-cannot-be-empty-key","errorCode":null,"errorMessage":"selectbox options cannot be empty: {key}","messagePattern":"selectbox options cannot be empty: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"webui/Main.py","lineNumber":1630,"sourceCode":"\n\ndef localized_widget_key(name, *parts):\n    # 部分 Streamlit selectbox 使用稳定 key 记住选择状态，但展示文本来自 locale。\n    # 语言切换时把语言也放进 key，可以强制重建控件，避免选中项仍显示旧语言。\n    language = st.session_state.get(\"ui_language\", config.ui.get(\"language\", \"\"))\n    suffix_parts = [name, language, *[str(part) for part in parts if part]]\n    return \"_\".join(suffix_parts)\n\n\ndef stable_selectbox(label, options, default_value, key, format_func=None, **kwargs):\n    # Streamlit 1.59 对 selectbox 的状态复用更敏感：如果控件没有固定 key，\n    # 或者真实选项只是一组临时下标，页面 rerun 后容易被重新计算的 index 覆盖，\n    # 表现为用户第一次选择不生效、需要再选一次。这个 helper 统一用稳定业务值\n    # 作为真实选项，并在 session_state 里保存该值；展示文案只通过 format_func\n    # 转换，避免翻译文案、选项顺序或上游配置变化影响选择状态。\n    options = list(options)\n    if not options:\n        raise ValueError(f\"selectbox options cannot be empty: {key}\")\n\n    if default_value not in options:\n        default_value = options[0]\n\n    widget_key = localized_widget_key(key)\n    selected_value = st.session_state.get(widget_key)\n    accepts_custom_value = bool(kwargs.get(\"accept_new_options\"))\n    has_valid_custom_value = (\n        accepts_custom_value\n        and isinstance(selected_value, str)\n        and bool(selected_value.strip())\n    )\n    if selected_value not in options and not has_valid_custom_value:\n        # 如果上游选项发生变化（例如切换 TTS provider 后声音列表变了），\n        # 旧值已经不合法。控件创建前直接初始化 session_state，之后只让 key\n        # 管理状态，不再同时传入 index。这样可以避免 Streamlit 在 rerun 时\n        # 用重新计算的 index 覆盖用户刚选择的值，导致第一次选择不生效。\n        st.session_state[widget_key] = default_value","sourceCodeStart":1612,"sourceCodeEnd":1648,"githubUrl":"https://github.com/harry0703/MoneyPrinterTurbo/blob/1f9f19c2021a68d04df228f33e9099a0c947f6f8/webui/Main.py#L1612-L1648","documentation":"stable_selectbox() is a WebUI helper that wraps Streamlit selectbox with stable business values as options (to survive reruns and language switches). It raises ValueError when the options list is empty, because a selectbox with zero options cannot render or keep state. The empty list almost always means an upstream data source (e.g. available voices, songs, providers) produced nothing at render time.","triggerScenarios":"Any call stable_selectbox(label, options=[], ...) — e.g. the list of BGM songs from storage/bgm + resource/songs is empty, a provider list filters to zero entries, or a config directory yields no items — while the page is being rendered.","commonSituations":"Fresh install with resource/songs and storage/bgm empty so the BGM picker has no candidates; a config file or directory the options derive from is missing/renamed; a filter (language, provider availability) removes every option; environment where a listing API returns nothing.","solutions":["Check why the upstream list is empty: e.g. for song/BGM pickers ensure resource/songs or storage/bgm actually contains readable files","Supply a sensible non-empty fallback or default option list at the call site instead of passing a possibly-empty generator result","If empty is legitimately possible, guard the call site: skip rendering the selectbox (and show an informative message) when options is empty rather than calling the helper"],"exampleFix":"# before\nstable_selectbox(tr(\"Background Music\"), songs, songs[0] if songs else None, key=\"bgm_song\")\n\n# after\nif not songs:\n    st.info(tr(\"No background music available. Upload a song first.\"))\nelse:\n    stable_selectbox(tr(\"Background Music\"), songs, songs[0], key=\"bgm_song\")","handlingStrategy":"type-guard","validationCode":"options = list(options)\nif not options:\n    st.info(\"No options available for this control yet.\")\n    return  # skip rendering instead of raising","typeGuard":"def has_select_options(options) -> bool:\n    \"\"\"Narrow before calling stable_selectbox.\"\"\"\n    return bool(list(options or []))","tryCatchPattern":"try:\n    value = stable_selectbox(label, options, default_value, key=key)\nexcept ValueError as exc:\n    if \"selectbox options cannot be empty\" in str(exc):\n        st.warning(f\"{label}: no options available\")\n        value = None\n    else:\n        raise","preventionTips":["Materialize generator/list options with list(...) and truth-check before every stable_selectbox call","Keep seed data present (e.g. at least one built-in song) so option lists are never empty on fresh installs","Log a warning with the widget key whenever an options source comes back empty, to catch config drift early"],"tags":["webui","streamlit","selectbox","empty-options","ui-state"],"backgroundTag":null,"analyzedSha":"1f9f19c2021a68d04df228f33e9099a0c947f6f8","analyzedAt":"2026-08-14T19:41:05.568Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}