Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 45 additions & 6 deletions src/ui/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@
)


def _rerun_app():
st.rerun()


def _settings_changed(
entry_mode_new: str,
current_role_new: str,
current_mode_new: str,
model_profile_new: str,
performance_mode_new: str,
atmosphere_new: str,
entry_mode_old: str,
current_role_old: str,
current_mode_old: str,
model_profile_old: str,
performance_mode_old: str,
atmosphere_old: str,
) -> bool:
return (
entry_mode_new != entry_mode_old
or current_role_new != current_role_old
or current_mode_new != current_mode_old
or model_profile_new != model_profile_old
or performance_mode_new != performance_mode_old
or atmosphere_new != atmosphere_old
)


def _switch_to_wechat_entry(unread_content: str, runtime_modes, session_state) -> None:
session_state.wechat_messages = unread_content
if runtime_modes.entry_mode != "wechat":
Expand Down Expand Up @@ -155,6 +183,14 @@ def render_sidebar():
apply_settings = st.form_submit_button("应用设置", use_container_width=True)

if apply_settings:
anything_changed = _settings_changed(
entry_mode_new, current_role_new, current_mode_new, model_profile_new,
performance_mode_new, atmosphere_new,
runtime_modes.entry_mode, st.session_state.current_role,
st.session_state.current_mode, st.session_state.model_profile,
runtime_modes.performance_mode, st.session_state.interaction_mode,
)

if entry_mode_new != runtime_modes.entry_mode:
update_entry_mode(entry_mode_new)
runtime_modes.entry_mode = entry_mode_new
Expand All @@ -174,8 +210,11 @@ def render_sidebar():
update_interaction_mode(atmosphere_new)
set_interaction_mode(st.session_state.session_id, atmosphere_new)

if anything_changed:
st.session_state.current_route = {}

st.session_state.sidebar_notice = "设置已应用"
st.rerun(scope="fragment")
_rerun_app()

st.markdown('<div class="sidebar-divider"></div>', unsafe_allow_html=True)

Expand Down Expand Up @@ -207,7 +246,7 @@ def render_sidebar():
update_safe_mode(safe_new)
runtime_modes.safe_mode = safe_new
st.session_state.sidebar_notice = "状态开关已应用"
st.rerun(scope="fragment")
_rerun_app()

st.markdown('<div class="sidebar-divider"></div>', unsafe_allow_html=True)

Expand Down Expand Up @@ -241,7 +280,7 @@ def render_sidebar():
"enabled" if capture_enabled_new else "disabled",
)
st.session_state.sidebar_notice = "记忆设置已应用"
st.rerun(scope="fragment")
_rerun_app()

st.markdown('<div class="sidebar-divider"></div>', unsafe_allow_html=True)

Expand Down Expand Up @@ -276,14 +315,14 @@ def render_sidebar():
has_unread = unread_content and "暂无未读" not in unread_content
if has_unread and st.button("查看未读消息", use_container_width=True):
_switch_to_wechat_entry(unread_content, runtime_modes, st.session_state)
st.rerun(scope="fragment")
_rerun_app()

if st.button("清空未读消息", use_container_width=True):
clear_wechat_unread()
set_wechat_unread_cleared(st.session_state.session_id)
st.session_state.wechat_messages = None
st.session_state.sidebar_notice = "未读消息已清空"
st.rerun(scope="fragment")
_rerun_app()

cols = st.columns(2)
with cols[0]:
Expand All @@ -297,7 +336,7 @@ def render_sidebar():
st.session_state.wechat_messages = None
st.session_state.health_report = health_report(force_refresh=True)
st.session_state.sidebar_notice = "配置、记忆与健康状态已刷新"
st.rerun(scope="fragment")
_rerun_app()

if st.session_state.get("health_report"):
with st.expander("健康报告", expanded=False):
Expand Down
114 changes: 114 additions & 0 deletions tests/test_sidebar_global_rerun.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Tests for sidebar global rerun behavior.

Verifies that global-affecting sidebar operations use full-page rerun
instead of fragment-scoped rerun, and that settings changes clear stale routes.
"""

from pathlib import Path


def test_sidebar_has_rerun_app():
text = Path("src/ui/sidebar.py").read_text(encoding="utf-8")
assert "def _rerun_app():" in text
assert "st.rerun()" in text


def test_sidebar_has_settings_changed_pure_function():
text = Path("src/ui/sidebar.py").read_text(encoding="utf-8")
assert "def _settings_changed(" in text


def test_no_fragment_rerun_in_sidebar():
"""All global-affecting operations must use _rerun_app, not fragment rerun."""
text = Path("src/ui/sidebar.py").read_text(encoding="utf-8")
assert 'st.rerun(scope="fragment")' not in text


def test_apply_settings_uses_rerun_app():
text = Path("src/ui/sidebar.py").read_text(encoding="utf-8")
lines = text.splitlines()
apply_idx = None
for i, line in enumerate(lines):
if "if apply_settings:" in line.strip():
apply_idx = i
break
assert apply_idx is not None, "apply_settings branch not found"
found = any("_rerun_app()" in lines[j] for j in range(apply_idx, apply_idx + 50))
assert found, "apply_settings block does not call _rerun_app()"


def test_apply_settings_clears_current_route():
text = Path("src/ui/sidebar.py").read_text(encoding="utf-8")
lines = text.splitlines()
apply_idx = None
for i, line in enumerate(lines):
if "if apply_settings:" in line.strip():
apply_idx = i
break
assert apply_idx is not None
block = "\n".join(lines[apply_idx : apply_idx + 50])
assert "anything_changed" in block
assert "current_route" in block


def test_settings_changed_no_change():
from src.ui.sidebar import _settings_changed

assert not _settings_changed(
"wechat", "march7", "auto", "flash", "standard", "standard",
"wechat", "march7", "auto", "flash", "standard", "standard",
)


def test_settings_changed_entry_mode():
from src.ui.sidebar import _settings_changed

assert _settings_changed(
"single", "march7", "auto", "flash", "standard", "standard",
"wechat", "march7", "auto", "flash", "standard", "standard",
)


def test_settings_changed_role():
from src.ui.sidebar import _settings_changed

assert _settings_changed(
"wechat", "keqing", "auto", "flash", "standard", "standard",
"wechat", "march7", "auto", "flash", "standard", "standard",
)


def test_settings_changed_mode():
from src.ui.sidebar import _settings_changed

assert _settings_changed(
"wechat", "march7", "论文", "flash", "standard", "standard",
"wechat", "march7", "auto", "flash", "standard", "standard",
)


def test_settings_changed_model():
from src.ui.sidebar import _settings_changed

assert _settings_changed(
"wechat", "march7", "auto", "pro", "standard", "standard",
"wechat", "march7", "auto", "flash", "standard", "standard",
)


def test_settings_changed_performance():
from src.ui.sidebar import _settings_changed

assert _settings_changed(
"wechat", "march7", "auto", "flash", "deep", "standard",
"wechat", "march7", "auto", "flash", "standard", "standard",
)


def test_settings_changed_atmosphere():
from src.ui.sidebar import _settings_changed

assert _settings_changed(
"wechat", "march7", "auto", "flash", "standard", "warm",
"wechat", "march7", "auto", "flash", "standard", "standard",
)
Loading