Skip to content

Commit 4e5b95a

Browse files
committed
feat(web): 持久化 LLM 模型配置 / Persist LLM config across browser sessions
Web UI 的 st.session_state 在标签页关闭后丢失,每次打开都回到默认 MiniMax。 本次修改在开始分析前自动保存 provider + quick/deep 模型选择到 .llm_config.json, 下次打开时恢复。 Persist LLM model selection (provider, quick/deep models, base URL) to a JSON file so it survives browser tab close/reopen. Previously st.session_state was lost, always resetting to the default MiniMax provider.
1 parent ed778c5 commit 4e5b95a

1 file changed

Lines changed: 58 additions & 0 deletions

File tree

web/components/sidebar.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
from __future__ import annotations
44

5+
import json
6+
import logging
57
import os
68
from datetime import date
9+
from pathlib import Path
710

811
import streamlit as st
912

@@ -17,6 +20,58 @@
1720
record_incomplete_task,
1821
)
1922

23+
logger = logging.getLogger(__name__)
24+
25+
# ── LLM config persistence ────────────────────────────────────────────────────
26+
# Saves model selection to a JSON file so it survives browser tab close/reopen.
27+
_LLM_CONFIG_PATH = Path(DEFAULT_CONFIG["project_dir"]) / ".llm_config.json"
28+
29+
30+
def _load_saved_llm_config() -> None:
31+
"""Restore user's last model selection into session_state defaults."""
32+
try:
33+
cfg = json.loads(_LLM_CONFIG_PATH.read_text())
34+
except (FileNotFoundError, json.JSONDecodeError):
35+
return
36+
provider_key = cfg.get("llm_provider", "")
37+
try:
38+
idx = _PROVIDER_KEYS.index(provider_key)
39+
except ValueError:
40+
idx = 0
41+
st.session_state.setdefault("llm_provider_idx", idx)
42+
st.session_state.setdefault("quick_model_idx", cfg.get("quick_model_idx", 0))
43+
st.session_state.setdefault("deep_model_idx", cfg.get("deep_model_idx", 0))
44+
st.session_state.setdefault("llm_base_url", cfg.get("llm_base_url", ""))
45+
st.session_state.setdefault("subscription_scope", cfg.get("subscription_scope", "off"))
46+
if cfg.get("agent_sdk_model"):
47+
st.session_state.setdefault("agent_sdk_model", cfg["agent_sdk_model"])
48+
for key in ("custom_quick_model", "custom_deep_model"):
49+
if key in cfg and cfg[key]:
50+
st.session_state.setdefault(key, cfg[key])
51+
52+
53+
def _save_llm_config() -> None:
54+
"""Persist current LLM config to disk (called before analysis)."""
55+
cfg = {
56+
"llm_provider": st.session_state.get("llm_provider", "minimax"),
57+
"quick_model_idx": st.session_state.get("quick_model_idx", 0),
58+
"deep_model_idx": st.session_state.get("deep_model_idx", 0),
59+
"llm_base_url": st.session_state.get("llm_base_url", ""),
60+
"subscription_scope": st.session_state.get("subscription_scope", "off"),
61+
}
62+
if st.session_state.get("agent_sdk_model"):
63+
cfg["agent_sdk_model"] = st.session_state["agent_sdk_model"]
64+
for key in ("custom_quick_model", "custom_deep_model"):
65+
val = st.session_state.get(key)
66+
if val:
67+
cfg[key] = val
68+
# 持久化失败(目录只读 / 磁盘满)只记 warning,不得打断「开始分析」主流程
69+
try:
70+
_LLM_CONFIG_PATH.write_text(json.dumps(cfg, ensure_ascii=False, indent=2))
71+
except OSError as exc:
72+
logger.warning("LLM 配置持久化失败(不影响本次分析): %s", exc)
73+
74+
2075
# Provider display names in recommended order
2176
_PROVIDERS: list[tuple[str, str]] = [
2277
("MiniMax(推荐·国内直连)", "minimax"),
@@ -246,6 +301,8 @@ def _render_llm_config() -> None:
246301

247302
def render_sidebar() -> None:
248303
"""Render the sidebar with input controls and history."""
304+
# Restore saved LLM config on every render so selectboxes start at the user's last selection.
305+
_load_saved_llm_config()
249306

250307
st.markdown(
251308
"""
@@ -303,6 +360,7 @@ def render_sidebar() -> None:
303360
disabled=is_busy or not ticker,
304361
type="primary",
305362
):
363+
_save_llm_config() # persist model choice before running
306364
resolved_code, err = _resolve_user_input(ticker)
307365
if err:
308366
st.error(f"❌ {err}")

0 commit comments

Comments
 (0)