fix(web_terminal): 程序启动自动恢复最近对话时不恢复模型,避免 /new 页面显示旧对话模型;显式加载对话时仍恢复保存的模型
This commit is contained in:
parent
58ec936a8c
commit
3c4ae917b2
@ -41,7 +41,7 @@ class WebTerminal(MainTerminal):
|
|||||||
latest = conversations[0]
|
latest = conversations[0]
|
||||||
conv_id = latest.get("id")
|
conv_id = latest.get("id")
|
||||||
if conv_id:
|
if conv_id:
|
||||||
result = self.load_conversation(conv_id)
|
result = self.load_conversation(conv_id, restore_model=False)
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
print(f"[WebTerminal] 已加载最近对话: {conv_id}")
|
print(f"[WebTerminal] 已加载最近对话: {conv_id}")
|
||||||
return
|
return
|
||||||
@ -311,12 +311,14 @@ class WebTerminal(MainTerminal):
|
|||||||
)
|
)
|
||||||
perf_log("_ensure_conversation_versioning_enabled done", elapsed_ms=(time.perf_counter() - t0) * 1000, extra={"conv_id": normalized_id})
|
perf_log("_ensure_conversation_versioning_enabled done", elapsed_ms=(time.perf_counter() - t0) * 1000, extra={"conv_id": normalized_id})
|
||||||
|
|
||||||
def load_conversation(self, conversation_id: str) -> Dict:
|
def load_conversation(self, conversation_id: str, restore_model: bool = True) -> Dict:
|
||||||
"""
|
"""
|
||||||
加载指定对话(Web版本)
|
加载指定对话(Web版本)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
conversation_id: 对话ID
|
conversation_id: 对话ID
|
||||||
|
restore_model: 是否从对话 metadata 恢复模型。程序启动时自动恢复最近对话不恢复模型,
|
||||||
|
避免 /new 页面显示旧对话模型;用户显式加载对话时恢复。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict: 加载结果
|
Dict: 加载结果
|
||||||
@ -351,12 +353,13 @@ class WebTerminal(MainTerminal):
|
|||||||
self.set_network_permission(network_permission)
|
self.set_network_permission(network_permission)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
saved_model_key = meta.get("model_key")
|
if restore_model:
|
||||||
if saved_model_key:
|
saved_model_key = meta.get("model_key")
|
||||||
try:
|
if saved_model_key:
|
||||||
self.set_model(saved_model_key)
|
try:
|
||||||
except Exception as exc:
|
self.set_model(saved_model_key)
|
||||||
logger.warning("加载对话模型 %s 失败: %s", saved_model_key, exc)
|
except Exception as exc:
|
||||||
|
logger.warning("加载对话模型 %s 失败: %s", saved_model_key, exc)
|
||||||
self.pending_permission_mode = str(meta.get("pending_permission_mode") or "").strip().lower() or None
|
self.pending_permission_mode = str(meta.get("pending_permission_mode") or "").strip().lower() or None
|
||||||
self.pending_execution_mode = str(meta.get("pending_execution_mode") or "").strip().lower() or None
|
self.pending_execution_mode = str(meta.get("pending_execution_mode") or "").strip().lower() or None
|
||||||
self.pending_network_permission = str(meta.get("pending_network_permission") or "").strip().lower() or None
|
self.pending_network_permission = str(meta.get("pending_network_permission") or "").strip().lower() or None
|
||||||
|
|||||||
154
test/test_conversation_model_persistence.py
Normal file
154
test/test_conversation_model_persistence.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
"""回归测试:对话模型持久化与 /new 页面默认模型行为。"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from server.context import _apply_workspace_personalization_preferences
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSession:
|
||||||
|
def __init__(self):
|
||||||
|
self._data = {}
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self._data.get(key, default)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._data[key] = value
|
||||||
|
|
||||||
|
|
||||||
|
class TestApplyWorkspacePersonalizationPreferences(unittest.TestCase):
|
||||||
|
def _make_terminal(self, model_key="default-model"):
|
||||||
|
terminal = MagicMock()
|
||||||
|
terminal.model_key = model_key
|
||||||
|
terminal._workspace_default_model_applied = False
|
||||||
|
|
||||||
|
def _apply_personalization_preferences(config, apply_default_model=True):
|
||||||
|
if apply_default_model:
|
||||||
|
default_model = (config or {}).get("default_model")
|
||||||
|
if default_model:
|
||||||
|
terminal.model_key = default_model
|
||||||
|
terminal.apply_personalization_preferences = MagicMock(side_effect=_apply_personalization_preferences)
|
||||||
|
return terminal
|
||||||
|
|
||||||
|
def _make_workspace(self):
|
||||||
|
workspace = MagicMock()
|
||||||
|
workspace.data_dir = tempfile.mkdtemp()
|
||||||
|
return workspace
|
||||||
|
|
||||||
|
@patch("server.context.load_personalization_config")
|
||||||
|
@patch("server.context.has_request_context", return_value=True)
|
||||||
|
def test_session_model_restored(self, _hrc, mock_load_config):
|
||||||
|
"""session 中保存了模型时,应恢复到该模型。"""
|
||||||
|
mock_load_config.return_value = {"default_model": "default-model"}
|
||||||
|
terminal = self._make_terminal(model_key="old-model")
|
||||||
|
terminal.set_model = MagicMock(side_effect=lambda mk: setattr(terminal, "model_key", mk))
|
||||||
|
workspace = self._make_workspace()
|
||||||
|
session = FakeSession()
|
||||||
|
session["model_key"] = "session-model"
|
||||||
|
|
||||||
|
with patch("server.context.session", session):
|
||||||
|
_apply_workspace_personalization_preferences(terminal, workspace)
|
||||||
|
|
||||||
|
terminal.set_model.assert_called_once_with("session-model")
|
||||||
|
self.assertEqual(session.get("model_key"), "session-model")
|
||||||
|
|
||||||
|
@patch("server.context.load_personalization_config")
|
||||||
|
@patch("server.context.has_request_context", return_value=True)
|
||||||
|
def test_default_model_applied_for_fresh_session(self, _hrc, mock_load_config):
|
||||||
|
"""没有 session 模型时,应应用默认模型(且仅一次)。"""
|
||||||
|
mock_load_config.return_value = {"default_model": "default-model"}
|
||||||
|
terminal = self._make_terminal(model_key="kimi-k2.6")
|
||||||
|
workspace = self._make_workspace()
|
||||||
|
session = FakeSession()
|
||||||
|
|
||||||
|
with patch("server.context.session", session):
|
||||||
|
_apply_workspace_personalization_preferences(terminal, workspace)
|
||||||
|
|
||||||
|
terminal.set_model.assert_not_called()
|
||||||
|
self.assertEqual(terminal.model_key, "default-model")
|
||||||
|
self.assertEqual(session.get("model_key"), "default-model")
|
||||||
|
self.assertTrue(terminal._workspace_default_model_applied)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoadConversationRestoreModel(unittest.TestCase):
|
||||||
|
@patch("core.web_terminal.logger")
|
||||||
|
def test_restore_model_true_restores_saved_model(self, _mock_logger):
|
||||||
|
"""显式加载对话时应恢复对话保存的模型。"""
|
||||||
|
from core.web_terminal import WebTerminal
|
||||||
|
|
||||||
|
terminal = MagicMock(spec=WebTerminal)
|
||||||
|
terminal.model_key = "default-model"
|
||||||
|
terminal.thinking_mode = False
|
||||||
|
terminal.run_mode = "fast"
|
||||||
|
terminal.multi_agent_mode = False
|
||||||
|
terminal.context_manager = MagicMock()
|
||||||
|
terminal.context_manager.load_conversation_by_id.return_value = True
|
||||||
|
|
||||||
|
cm = MagicMock()
|
||||||
|
cm.load_conversation.return_value = {
|
||||||
|
"metadata": {
|
||||||
|
"thinking_mode": False,
|
||||||
|
"model_key": "saved-model",
|
||||||
|
"multi_agent_mode": False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal.context_manager._get_conversation_manager_for_id.return_value = cm
|
||||||
|
|
||||||
|
terminal.set_model = MagicMock(side_effect=lambda mk: setattr(terminal, "model_key", mk))
|
||||||
|
terminal.set_permission_mode = MagicMock()
|
||||||
|
terminal.set_execution_mode = MagicMock()
|
||||||
|
terminal.set_network_permission = MagicMock()
|
||||||
|
terminal.api_client = MagicMock()
|
||||||
|
terminal.current_session_id = 0
|
||||||
|
|
||||||
|
# 调用实际方法
|
||||||
|
result = WebTerminal.load_conversation(terminal, "conv_test_001", restore_model=True)
|
||||||
|
|
||||||
|
terminal.set_model.assert_called_once_with("saved-model")
|
||||||
|
self.assertEqual(terminal.model_key, "saved-model")
|
||||||
|
self.assertTrue(result.get("success"))
|
||||||
|
|
||||||
|
@patch("core.web_terminal.logger")
|
||||||
|
def test_restore_model_false_keeps_current_model(self, _mock_logger):
|
||||||
|
"""程序启动自动恢复最近对话时不应恢复模型,避免 /new 页面显示旧模型。"""
|
||||||
|
from core.web_terminal import WebTerminal
|
||||||
|
|
||||||
|
terminal = MagicMock(spec=WebTerminal)
|
||||||
|
terminal.model_key = "default-model"
|
||||||
|
terminal.thinking_mode = False
|
||||||
|
terminal.run_mode = "fast"
|
||||||
|
terminal.multi_agent_mode = False
|
||||||
|
terminal.context_manager = MagicMock()
|
||||||
|
terminal.context_manager.load_conversation_by_id.return_value = True
|
||||||
|
|
||||||
|
cm = MagicMock()
|
||||||
|
cm.load_conversation.return_value = {
|
||||||
|
"metadata": {
|
||||||
|
"thinking_mode": False,
|
||||||
|
"model_key": "saved-model",
|
||||||
|
"multi_agent_mode": False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminal.context_manager._get_conversation_manager_for_id.return_value = cm
|
||||||
|
|
||||||
|
terminal.set_model = MagicMock(side_effect=lambda mk: setattr(terminal, "model_key", mk))
|
||||||
|
terminal.set_permission_mode = MagicMock()
|
||||||
|
terminal.set_execution_mode = MagicMock()
|
||||||
|
terminal.set_network_permission = MagicMock()
|
||||||
|
terminal.api_client = MagicMock()
|
||||||
|
terminal.current_session_id = 0
|
||||||
|
|
||||||
|
result = WebTerminal.load_conversation(terminal, "conv_test_001", restore_model=False)
|
||||||
|
|
||||||
|
terminal.set_model.assert_not_called()
|
||||||
|
self.assertEqual(terminal.model_key, "default-model")
|
||||||
|
self.assertTrue(result.get("success"))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in New Issue
Block a user