feat: 移除实验性间隔思考模式,推理强度前置到前端
- 彻底移除「每 N 次请求强制思考」的间隔调度机制(思考/非思考交替导致缓存命中率极低) - 运行模式收敛为 fast/thinking:原 deep 改名 thinking,历史数据读取时映射 - 新增推理强度五档滑块(low/medium/high/xhigh/max + 默认不传参),置于模型/模式弹窗 - 模型配置新增 reasoning_effort 开关;档位会话级持久化到对话 meta,随对话保存/恢复 - 个人空间新增「默认推理强度」;新用户默认运行模式改为 thinking - 滑块交互:JS 插值驱动(点击/拖动/吸附全程平滑),首帧档位语义定位无闪烁 - 保存链路:前端防抖 600ms + conversation_id 兜底,修复 /new 发消息丢档位、切对话恢复错误
This commit is contained in:
parent
496bbe4f97
commit
c9c3eedd82
@ -8,6 +8,7 @@
|
||||
"apikey": "${API_KEY_KIMI_OFFICIAL}",
|
||||
"multimodal": "image,video",
|
||||
"reasoning_capability": "fast,thinking",
|
||||
"reasoning_effort": true,
|
||||
"context_window": 256000,
|
||||
"max_output_tokens": 32768,
|
||||
"thinkmode_status": {
|
||||
|
||||
@ -20,7 +20,9 @@ MAX_ITERATIONS_PER_TASK = None
|
||||
MAX_CONSECUTIVE_SAME_TOOL = None
|
||||
MAX_TOTAL_TOOL_CALLS = None
|
||||
TOOL_CALL_COOLDOWN = 0.5
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
# 推理强度档位(模型配置 supports_reasoning_effort 时,思考模式下可选;None 表示默认不传参)
|
||||
REASONING_EFFORT_LEVELS = ("low", "medium", "high", "xhigh", "max")
|
||||
|
||||
# 工具字符/体积限制
|
||||
MAX_READ_FILE_CHARS = 50000
|
||||
@ -94,7 +96,7 @@ __all__ = [
|
||||
"MAX_CONSECUTIVE_SAME_TOOL",
|
||||
"MAX_TOTAL_TOOL_CALLS",
|
||||
"TOOL_CALL_COOLDOWN",
|
||||
"THINKING_FAST_INTERVAL",
|
||||
"REASONING_EFFORT_LEVELS",
|
||||
"MAX_READ_FILE_CHARS",
|
||||
"MAX_FOCUS_FILE_CHARS",
|
||||
"MAX_RUN_COMMAND_CHARS",
|
||||
|
||||
@ -58,10 +58,10 @@ def _to_optional_int(value: Any) -> Optional[int]:
|
||||
def _reasoning_flags(capability: str) -> Dict[str, bool]:
|
||||
normalized = str(capability or "fast").replace(" ", "").lower()
|
||||
if normalized == "thinking":
|
||||
return {"supports_thinking": True, "fast_only": False, "deep_only": True}
|
||||
return {"supports_thinking": True, "fast_only": False, "thinking_only": True}
|
||||
if normalized == "fast,thinking":
|
||||
return {"supports_thinking": True, "fast_only": False, "deep_only": False}
|
||||
return {"supports_thinking": False, "fast_only": True, "deep_only": False}
|
||||
return {"supports_thinking": True, "fast_only": False, "thinking_only": False}
|
||||
return {"supports_thinking": False, "fast_only": True, "thinking_only": False}
|
||||
|
||||
|
||||
def _build_custom_profile(item: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
@ -131,8 +131,11 @@ def _build_custom_profile(item: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"supports_thinking": bool(flags["supports_thinking"]),
|
||||
"fast_only": bool(flags["fast_only"]),
|
||||
}
|
||||
if flags["deep_only"]:
|
||||
profile["deep_only"] = True
|
||||
if flags["thinking_only"]:
|
||||
profile["thinking_only"] = True
|
||||
# 推理强度开关:配置为真值(如 "reasoning_effort": true)时,思考模式下前端可选档位
|
||||
if bool(item.get("reasoning_effort")):
|
||||
profile["supports_reasoning_effort"] = True
|
||||
if flags["supports_thinking"]:
|
||||
profile["thinking"] = {
|
||||
"base_url": base_url.rstrip("/"),
|
||||
@ -231,7 +234,8 @@ def get_model_capabilities(key: str) -> Dict[str, Any]:
|
||||
"multimodal": multimodal,
|
||||
"supports_thinking": bool(profile.get("supports_thinking", False)),
|
||||
"fast_only": bool(profile.get("fast_only", False)),
|
||||
"deep_only": bool(profile.get("deep_only", False)),
|
||||
"thinking_only": bool(profile.get("thinking_only", False)),
|
||||
"supports_reasoning_effort": bool(profile.get("supports_reasoning_effort", False)),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -57,10 +57,6 @@ from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
from modules.mcp_client_manager import MCPClientManager
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
from utils.api_client import DeepSeekClient
|
||||
@ -90,20 +86,22 @@ class MainTerminal(MainTerminalCommandMixin, MainTerminalContextMixin, MainTermi
|
||||
usage_tracker: Optional[object] = None,
|
||||
):
|
||||
self.project_path = project_path
|
||||
allowed_modes = {"fast", "thinking", "deep"}
|
||||
allowed_modes = {"fast", "thinking"}
|
||||
if run_mode == "deep": # 旧版标识符映射
|
||||
run_mode = "thinking"
|
||||
initial_mode = run_mode if run_mode in allowed_modes else ("thinking" if thinking_mode else "fast")
|
||||
self.run_mode = initial_mode
|
||||
self.thinking_mode = initial_mode != "fast" # False=快速模式, True=思考模式
|
||||
self.deep_thinking_mode = initial_mode == "deep"
|
||||
# 记录因 fast-only 模型被强制切到 fast 前的模式,便于切回可思考模型时恢复
|
||||
self._mode_before_fast_only: Optional[str] = None
|
||||
# 会话级推理强度(None=默认,不传参)
|
||||
self.reasoning_effort: Optional[str] = None
|
||||
self.data_dir = Path(data_dir).expanduser().resolve() if data_dir else Path(DATA_DIR).resolve()
|
||||
self.usage_tracker = usage_tracker
|
||||
|
||||
# 初始化组件
|
||||
self.api_client = DeepSeekClient(thinking_mode=self.thinking_mode)
|
||||
self.api_client.project_path = project_path
|
||||
self.api_client.set_deep_thinking_mode(self.deep_thinking_mode)
|
||||
self.model_key = get_default_model_key()
|
||||
self.model_profile = get_model_profile(self.model_key)
|
||||
self.apply_model_profile(self.model_profile)
|
||||
@ -200,7 +198,6 @@ class MainTerminal(MainTerminalCommandMixin, MainTerminalContextMixin, MainTermi
|
||||
self.disabled_tools: Set[str] = set()
|
||||
self.disabled_notice_tools: Set[str] = set()
|
||||
self._refresh_disabled_tools()
|
||||
self.thinking_fast_interval = THINKING_FAST_INTERVAL
|
||||
self.default_disabled_tool_categories: List[str] = []
|
||||
# 个性化工具意图开关
|
||||
self.tool_intent_enabled: bool = False
|
||||
|
||||
@ -65,9 +65,9 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
from config.limits import REASONING_EFFORT_LEVELS
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
REASONING_EFFORT_LEVELS = ("low", "medium", "high", "xhigh", "max")
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
@ -156,11 +156,6 @@ class MainTerminalCommandMixin:
|
||||
async def handle_task(self, user_input: str):
|
||||
"""处理用户任务(完全修复版:彻底解决对话记录重复问题)"""
|
||||
try:
|
||||
# 如果是思考模式,每个新任务重置状态
|
||||
# 注意:这里重置的是当前任务的第一次调用标志,确保新用户请求重新思考
|
||||
if self.thinking_mode:
|
||||
self.api_client.start_new_task(force_deep=self.deep_thinking_mode)
|
||||
|
||||
# 新增:开始新的任务会话
|
||||
self.current_session_id += 1
|
||||
|
||||
@ -302,9 +297,12 @@ class MainTerminalCommandMixin:
|
||||
# 保存响应内容
|
||||
final_response = response
|
||||
|
||||
# 获取思考内容(如果有的话)
|
||||
if self.api_client.current_task_thinking:
|
||||
final_thinking = self.api_client.current_task_thinking
|
||||
# 获取思考内容(如果有的话):取最后一条 assistant 消息的 reasoning_content
|
||||
if not final_thinking:
|
||||
for _msg in reversed(messages):
|
||||
if _msg.get("role") == "assistant" and _msg.get("reasoning_content"):
|
||||
final_thinking = _msg["reasoning_content"]
|
||||
break
|
||||
|
||||
# ===== 统一保存到对话历史(关键修复) =====
|
||||
|
||||
@ -449,10 +447,6 @@ class MainTerminalCommandMixin:
|
||||
print(f"{OUTPUT_FORMATS['success']} 对话已加载: {conversation_id}")
|
||||
print(f"{OUTPUT_FORMATS['info']} 消息数量: {len(self.context_manager.conversation_history)}")
|
||||
|
||||
# 如果是思考模式,重置状态(下次任务会重新思考)
|
||||
if self.thinking_mode:
|
||||
self.api_client.start_new_task(force_deep=self.deep_thinking_mode)
|
||||
|
||||
self.current_session_id += 1
|
||||
|
||||
else:
|
||||
@ -471,10 +465,6 @@ class MainTerminalCommandMixin:
|
||||
|
||||
print(f"{OUTPUT_FORMATS['success']} 已创建新对话: {conversation_id}")
|
||||
|
||||
# 重置相关状态
|
||||
if self.thinking_mode:
|
||||
self.api_client.start_new_task(force_deep=self.deep_thinking_mode)
|
||||
|
||||
self.current_session_id += 1
|
||||
|
||||
except Exception as e:
|
||||
@ -523,8 +513,6 @@ class MainTerminalCommandMixin:
|
||||
|
||||
# 思考模式状态
|
||||
thinking_status = self.get_run_mode_label()
|
||||
if self.thinking_mode:
|
||||
thinking_status += f" ({'等待新任务' if self.api_client.current_task_first_call else '任务进行中'})"
|
||||
|
||||
# 新增:对话统计
|
||||
conversation_stats = self.context_manager.get_conversation_statistics()
|
||||
@ -761,32 +749,36 @@ class MainTerminalCommandMixin:
|
||||
print(f"\n总计: {structure['total_files']} 个文件, {structure['total_size'] / 1024 / 1024:.2f} MB")
|
||||
|
||||
def set_run_mode(self, mode: str) -> str:
|
||||
"""统一设置运行模式"""
|
||||
allowed = ["fast", "thinking", "deep"]
|
||||
"""统一设置运行模式(fast / thinking;旧值 deep 映射为 thinking)"""
|
||||
normalized = mode.lower()
|
||||
if normalized == "deep": # 旧版“深度思考”标识符,统一映射为思考模式
|
||||
normalized = "thinking"
|
||||
allowed = ["fast", "thinking"]
|
||||
if normalized not in allowed:
|
||||
raise ValueError(f"不支持的模式: {mode}")
|
||||
# 仅深度思考模型限制
|
||||
if getattr(self, "model_profile", {}).get("deep_only") and normalized != "deep":
|
||||
raise ValueError("当前模型仅支持深度思考模式")
|
||||
# 仅思考模型限制
|
||||
if getattr(self, "model_profile", {}).get("thinking_only") and normalized != "thinking":
|
||||
raise ValueError("当前模型仅支持思考模式")
|
||||
# fast-only 模型限制
|
||||
if getattr(self, "model_profile", {}).get("fast_only") and normalized != "fast":
|
||||
raise ValueError("当前模型仅支持快速模式")
|
||||
previous_mode = getattr(self, "run_mode", "fast")
|
||||
self.run_mode = normalized
|
||||
self.thinking_mode = normalized != "fast"
|
||||
self.deep_thinking_mode = normalized == "deep"
|
||||
self.api_client.thinking_mode = self.thinking_mode
|
||||
self.api_client.set_deep_thinking_mode(self.deep_thinking_mode)
|
||||
if self.deep_thinking_mode:
|
||||
self.api_client.force_thinking_next_call = False
|
||||
self.api_client.skip_thinking_next_call = False
|
||||
if not self.thinking_mode:
|
||||
self.api_client.start_new_task()
|
||||
elif previous_mode == "deep" and normalized != "deep":
|
||||
self.api_client.start_new_task()
|
||||
return self.run_mode
|
||||
|
||||
def set_reasoning_effort(self, effort: Optional[str]) -> Optional[str]:
|
||||
"""设置会话级推理强度;None 表示默认(请求中不传该参数)。"""
|
||||
if effort is None or str(effort).strip() == "":
|
||||
self.reasoning_effort = None
|
||||
else:
|
||||
normalized = str(effort).strip().lower()
|
||||
if normalized not in REASONING_EFFORT_LEVELS:
|
||||
raise ValueError(f"不支持的推理强度: {effort}")
|
||||
self.reasoning_effort = normalized
|
||||
self.api_client.reasoning_effort = self.reasoning_effort
|
||||
return self.reasoning_effort
|
||||
|
||||
def apply_model_profile(self, profile: dict):
|
||||
"""将模型配置应用到 API 客户端"""
|
||||
if not profile:
|
||||
@ -810,12 +802,12 @@ class MainTerminalCommandMixin:
|
||||
# fast-only 模型强制快速模式
|
||||
if profile.get("fast_only") and self.run_mode != "fast":
|
||||
# 记住被强制前的模式,切回支持思考的模型时可恢复
|
||||
if previous_mode in {"thinking", "deep"}:
|
||||
if previous_mode == "thinking":
|
||||
self._mode_before_fast_only = previous_mode
|
||||
self.set_run_mode("fast")
|
||||
# 仅深度思考模型强制 deep
|
||||
if profile.get("deep_only") and self.run_mode != "deep":
|
||||
self.set_run_mode("deep")
|
||||
# 仅思考模型强制思考模式
|
||||
if profile.get("thinking_only") and self.run_mode != "thinking":
|
||||
self.set_run_mode("thinking")
|
||||
# 从 fast-only 模型切回支持思考的模型时,恢复先前模式(若可用)
|
||||
if (
|
||||
not profile.get("fast_only")
|
||||
@ -823,7 +815,7 @@ class MainTerminalCommandMixin:
|
||||
and profile.get("supports_thinking")
|
||||
):
|
||||
restore_mode = getattr(self, "_mode_before_fast_only", None)
|
||||
if restore_mode in {"thinking", "deep"}:
|
||||
if restore_mode == "thinking":
|
||||
try:
|
||||
self.set_run_mode(restore_mode)
|
||||
except ValueError:
|
||||
@ -832,25 +824,22 @@ class MainTerminalCommandMixin:
|
||||
self._mode_before_fast_only = None
|
||||
try:
|
||||
logger.info(
|
||||
"[ModelSwitch] model=%s run_mode=%s thinking=%s deep=%s fast_only=%s deep_only=%s",
|
||||
"[ModelSwitch] model=%s run_mode=%s thinking=%s fast_only=%s thinking_only=%s",
|
||||
self.model_key,
|
||||
self.run_mode,
|
||||
self.thinking_mode,
|
||||
self.deep_thinking_mode,
|
||||
bool(profile.get("fast_only")),
|
||||
bool(profile.get("deep_only")),
|
||||
bool(profile.get("thinking_only")),
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
# 如果模型支持思考,但当前 run_mode 为 thinking/deep,则保持;否则无需调整
|
||||
self.api_client.start_new_task(force_deep=self.deep_thinking_mode)
|
||||
# 如果模型支持思考,则保持当前 run_mode;否则无需调整
|
||||
return self.model_key
|
||||
|
||||
def get_run_mode_label(self) -> str:
|
||||
labels = {
|
||||
"fast": "快速模式(无思考)",
|
||||
"thinking": "思考模式(首次调用使用思考模型)",
|
||||
"deep": "深度思考模式(整轮使用思考模型)"
|
||||
"thinking": "思考模式(使用思考模型)"
|
||||
}
|
||||
return labels.get(self.run_mode, "快速模式(无思考)")
|
||||
|
||||
|
||||
@ -67,10 +67,6 @@ from modules.skills_manager import (
|
||||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -67,10 +67,6 @@ from modules.skills_manager import (
|
||||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -69,10 +69,6 @@ from modules.skills_manager import (
|
||||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -68,10 +68,6 @@ from modules.skills_manager import (
|
||||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -67,10 +67,6 @@ from modules.skills_manager import (
|
||||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -67,10 +67,6 @@ from modules.skills_manager import (
|
||||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -65,10 +65,6 @@ from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import MCPServerRegistry, build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -92,10 +92,6 @@ from modules.skills_manager import (
|
||||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -67,9 +67,9 @@ from modules.custom_tool_executor import CustomToolExecutor
|
||||
from modules.mcp_server_registry import build_default_mcp_category
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
from config.limits import REASONING_EFFORT_LEVELS
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
REASONING_EFFORT_LEVELS = ("low", "medium", "high", "xhigh", "max")
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
@ -116,8 +116,13 @@ class MainTerminalToolsPolicyMixin:
|
||||
continue
|
||||
self.tool_category_states[key] = bool(value)
|
||||
|
||||
def apply_personalization_preferences(self, config: Optional[Dict[str, Any]] = None, *, apply_default_model: bool = True):
|
||||
"""Apply persisted personalization settings that affect runtime behavior."""
|
||||
def apply_personalization_preferences(self, config: Optional[Dict[str, Any]] = None, *, apply_default_model: bool = True, apply_default_modes: bool = True):
|
||||
"""Apply persisted personalization settings that affect runtime behavior.
|
||||
|
||||
apply_default_modes=False 时跳过 default_model / default_run_mode /
|
||||
default_reasoning_effort 三项默认值应用——对话加载链路必须传 False,
|
||||
三者以对话 meta 为权威,默认值仅用于创建空对话。
|
||||
"""
|
||||
try:
|
||||
effective_config = config or load_personalization_config(self.data_dir)
|
||||
except Exception:
|
||||
@ -147,12 +152,6 @@ class MainTerminalToolsPolicyMixin:
|
||||
except Exception:
|
||||
self.enabled_skill_ids = set()
|
||||
|
||||
interval = effective_config.get("thinking_interval")
|
||||
if isinstance(interval, int) and interval > 0:
|
||||
self.thinking_fast_interval = interval
|
||||
else:
|
||||
self.thinking_fast_interval = THINKING_FAST_INTERVAL
|
||||
|
||||
disabled_categories = []
|
||||
raw_disabled = effective_config.get("disabled_tool_categories")
|
||||
if isinstance(raw_disabled, list):
|
||||
@ -176,21 +175,37 @@ class MainTerminalToolsPolicyMixin:
|
||||
|
||||
# 默认模型偏好(优先应用,再处理运行模式)
|
||||
preferred_model = effective_config.get("default_model")
|
||||
if apply_default_model and isinstance(preferred_model, str) and preferred_model != self.model_key:
|
||||
if apply_default_modes and apply_default_model and isinstance(preferred_model, str) and preferred_model != self.model_key:
|
||||
try:
|
||||
self.set_model(preferred_model)
|
||||
except Exception as exc:
|
||||
logger.warning("忽略无效默认模型: %s (%s)", preferred_model, exc)
|
||||
|
||||
preferred_mode = effective_config.get("default_run_mode")
|
||||
if isinstance(preferred_mode, str):
|
||||
if apply_default_modes and isinstance(preferred_mode, str):
|
||||
normalized_mode = preferred_mode.strip().lower()
|
||||
if normalized_mode in {"fast", "thinking", "deep"} and normalized_mode != self.run_mode:
|
||||
if normalized_mode == "deep": # 旧版标识符映射
|
||||
normalized_mode = "thinking"
|
||||
if normalized_mode in {"fast", "thinking"} and normalized_mode != self.run_mode:
|
||||
try:
|
||||
self.set_run_mode(normalized_mode)
|
||||
except ValueError:
|
||||
logger.warning("忽略无效默认运行模式: %s", preferred_mode)
|
||||
|
||||
# 默认推理强度(None=默认,不传参)
|
||||
if apply_default_modes:
|
||||
preferred_effort = effective_config.get("default_reasoning_effort")
|
||||
if isinstance(preferred_effort, str):
|
||||
preferred_effort = preferred_effort.strip().lower() or None
|
||||
if preferred_effort not in REASONING_EFFORT_LEVELS:
|
||||
preferred_effort = None
|
||||
else:
|
||||
preferred_effort = None
|
||||
try:
|
||||
self.set_reasoning_effort(preferred_effort)
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
|
||||
# 静默禁用工具提示
|
||||
self.silent_tool_disable = bool(effective_config.get("silent_tool_disable"))
|
||||
permission_mode = effective_config.get("default_permission_mode")
|
||||
|
||||
@ -69,10 +69,6 @@ from modules.skills_manager import (
|
||||
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
|
||||
from modules.custom_tool_executor import CustomToolExecutor
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
|
||||
@ -11,14 +11,14 @@ from modules.personalization_manager import load_personalization_config
|
||||
from modules.versioning_manager import ConversationVersioningManager, VersioningError
|
||||
from utils.perf_log import perf_log, PerfTimer
|
||||
try:
|
||||
from config import MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE
|
||||
from config import MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE, REASONING_EFFORT_LEVELS
|
||||
except ImportError:
|
||||
import sys
|
||||
from pathlib import Path
|
||||
project_root = Path(__file__).resolve().parents[1]
|
||||
if str(project_root) not in sys.path:
|
||||
sys.path.insert(0, str(project_root))
|
||||
from config import MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE
|
||||
from config import MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE, REASONING_EFFORT_LEVELS
|
||||
from modules.terminal_manager import TerminalManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -173,6 +173,13 @@ class WebTerminal(MainTerminal):
|
||||
preferred_model = prefs.get("default_model")
|
||||
preferred_mode = prefs.get("default_run_mode")
|
||||
preferred_permission_mode = prefs.get("default_permission_mode") or None
|
||||
preferred_effort = prefs.get("default_reasoning_effort")
|
||||
if isinstance(preferred_effort, str):
|
||||
preferred_effort = preferred_effort.strip().lower() or None
|
||||
if preferred_effort not in REASONING_EFFORT_LEVELS:
|
||||
preferred_effort = None
|
||||
else:
|
||||
preferred_effort = None
|
||||
if preferred_permission_mode not in ("readonly", "approval", "auto_approval", "unrestricted"):
|
||||
try:
|
||||
preferred_permission_mode = self.get_permission_mode()
|
||||
@ -180,14 +187,21 @@ class WebTerminal(MainTerminal):
|
||||
preferred_permission_mode = None
|
||||
if not isinstance(preferred_permission_mode, str) or not preferred_permission_mode.strip():
|
||||
preferred_permission_mode = "unrestricted"
|
||||
if isinstance(preferred_mode, str) and preferred_mode.lower() in {"fast", "thinking", "deep"}:
|
||||
candidate_mode = preferred_mode.lower() if isinstance(preferred_mode, str) else None
|
||||
if candidate_mode == "deep": # 旧版标识符映射
|
||||
candidate_mode = "thinking"
|
||||
if candidate_mode in {"fast", "thinking"}:
|
||||
try:
|
||||
self.set_run_mode(preferred_mode.lower())
|
||||
self.set_run_mode(candidate_mode)
|
||||
except ValueError as exc:
|
||||
logger.warning("忽略无效默认运行模式 %s: %s", preferred_mode, exc)
|
||||
else:
|
||||
# 未配置默认模式时回到快速模式
|
||||
self.set_run_mode("fast")
|
||||
try:
|
||||
self.set_reasoning_effort(preferred_effort)
|
||||
except ValueError:
|
||||
self.set_reasoning_effort(None)
|
||||
try:
|
||||
self.set_permission_mode(preferred_permission_mode, persist=False)
|
||||
except Exception:
|
||||
@ -217,6 +231,9 @@ class WebTerminal(MainTerminal):
|
||||
"execution_mode": self.get_execution_mode() if hasattr(self, "get_execution_mode") else "sandbox",
|
||||
"pending_permission_mode": None,
|
||||
"pending_execution_mode": None,
|
||||
# 推理强度随创建写入一次:prefer_defaults 时 self 已应用个性化默认值;
|
||||
# 显式模式(/new 页发消息)时 self 即用户当前档位,两者都直接沿用
|
||||
"reasoning_effort": getattr(self, "reasoning_effort", None),
|
||||
# frozen_*_prompt 不在创建时预设,由第一次 build_messages 根据当时的实际模式懒加载并冻结
|
||||
}
|
||||
if isinstance(metadata_overrides, dict):
|
||||
@ -266,10 +283,6 @@ class WebTerminal(MainTerminal):
|
||||
logger.warning("新对话应用默认版本控制失败: %s", exc)
|
||||
perf_log("create_new_conversation after default versioning", elapsed_ms=(time.perf_counter() - t0) * 1000, extra={"conv_id": conversation_id})
|
||||
|
||||
# 重置相关状态
|
||||
if self.thinking_mode:
|
||||
self.api_client.start_new_task()
|
||||
|
||||
self.current_session_id += 1
|
||||
|
||||
perf_log("create_new_conversation done", elapsed_ms=(time.perf_counter() - t0) * 1000, extra={"conv_id": conversation_id})
|
||||
@ -361,15 +374,25 @@ class WebTerminal(MainTerminal):
|
||||
try:
|
||||
success = self.context_manager.load_conversation_by_id(conversation_id)
|
||||
if success:
|
||||
# 根据对话元数据同步思考模式
|
||||
# 根据对话元数据同步运行模式与推理强度
|
||||
try:
|
||||
target_manager = self.context_manager._get_conversation_manager_for_id(conversation_id)
|
||||
conv_data = target_manager.load_conversation(conversation_id) or {}
|
||||
meta = conv_data.get("metadata", {}) or {}
|
||||
mode = bool(meta.get("thinking_mode", self.thinking_mode))
|
||||
self.thinking_mode = mode
|
||||
self.api_client.thinking_mode = mode
|
||||
self.api_client.start_new_task()
|
||||
saved_mode = str(meta.get("run_mode") or "").strip().lower()
|
||||
if saved_mode == "deep": # 旧版标识符映射
|
||||
saved_mode = "thinking"
|
||||
if saved_mode in {"fast", "thinking"}:
|
||||
self.run_mode = saved_mode
|
||||
self.thinking_mode = saved_mode != "fast"
|
||||
else:
|
||||
self.thinking_mode = bool(meta.get("thinking_mode", self.thinking_mode))
|
||||
self.run_mode = "thinking" if self.thinking_mode else "fast"
|
||||
self.api_client.thinking_mode = self.thinking_mode
|
||||
try:
|
||||
self.set_reasoning_effort(meta.get("reasoning_effort"))
|
||||
except ValueError:
|
||||
self.set_reasoning_effort(None)
|
||||
permission_mode = str(meta.get("permission_mode") or "").strip().lower()
|
||||
if permission_mode:
|
||||
try:
|
||||
@ -525,6 +548,7 @@ class WebTerminal(MainTerminal):
|
||||
"thinking_mode": self.thinking_mode,
|
||||
"thinking_status": self.get_thinking_mode_status(),
|
||||
"run_mode": self.run_mode,
|
||||
"reasoning_effort": getattr(self, "reasoning_effort", None),
|
||||
"model_key": getattr(self, "model_key", None),
|
||||
"permission_mode": self.get_permission_mode() if hasattr(self, "get_permission_mode") else "unrestricted",
|
||||
"execution_mode": self.get_execution_mode_state() if hasattr(self, "get_execution_mode_state") else {"mode": "sandbox"},
|
||||
|
||||
473
demo/reasoning_effort_slider.html
Normal file
473
demo/reasoning_effort_slider.html
Normal file
@ -0,0 +1,473 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>推理强度选择器 Demo</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
:root {
|
||||
--page-bg: #1a1a1c;
|
||||
--card-bg: #2c2c2e;
|
||||
--track-bg: #3a3a3c;
|
||||
--text-primary: #f2f2f4;
|
||||
--text-secondary: #9c9ca0;
|
||||
--knob-bg: #ffffff;
|
||||
--dot-on: rgba(255, 255, 255, 0.55);
|
||||
--dot-off: rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
background: var(--page-bg);
|
||||
font-family: -apple-system, "PingFang SC", "Helvetica Neue", sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* ========== 卡片(模拟弹窗内的一块区域) ========== */
|
||||
.effort-card {
|
||||
width: 460px;
|
||||
background: var(--card-bg);
|
||||
border-radius: 18px;
|
||||
padding: 22px 26px 20px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
/* ========== 顶部:两种状态文字叠在一起,淡入淡出切换 ========== */
|
||||
.effort-header {
|
||||
position: relative;
|
||||
height: 26px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.header-state {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
transition: opacity 0.18s ease, transform 0.18s ease;
|
||||
}
|
||||
.header-idle { opacity: 1; }
|
||||
.header-drag {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
pointer-events: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: 16px;
|
||||
}
|
||||
.effort-card.live-drag .header-idle {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
pointer-events: none;
|
||||
}
|
||||
.effort-card.live-drag .header-drag {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* ========== 「默认」打勾框:个人空间 fancy-check 缩小版 ========== */
|
||||
.default-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.default-label {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.default-toggle .fancy-check {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.default-toggle .fancy-check svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
overflow: visible;
|
||||
}
|
||||
.fancy-path {
|
||||
fill: none;
|
||||
stroke: var(--text-secondary);
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
transition:
|
||||
stroke-dasharray 0.5s ease,
|
||||
stroke-dashoffset 0.5s ease,
|
||||
stroke 0.2s ease;
|
||||
stroke-dasharray: 241 9999999;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
.default-toggle.checked .fancy-path {
|
||||
stroke-dasharray: 70.5096664428711 9999999;
|
||||
stroke-dashoffset: -262.2723388671875;
|
||||
}
|
||||
|
||||
/* ========== 滑块区域 ========== */
|
||||
.slider-zone {
|
||||
position: relative;
|
||||
transition: filter 0.25s ease, opacity 0.25s ease;
|
||||
}
|
||||
/* 默认勾选时:整体置灰,且停掉所有动画 */
|
||||
.effort-card.is-default .slider-zone {
|
||||
filter: grayscale(1) brightness(0.72);
|
||||
opacity: 0.55;
|
||||
}
|
||||
.effort-card.is-default .fill-layer {
|
||||
animation: none;
|
||||
}
|
||||
.effort-card.is-default .fill-layer::after {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.track {
|
||||
position: relative;
|
||||
height: 34px;
|
||||
border-radius: 17px;
|
||||
background: var(--track-bg);
|
||||
cursor: pointer;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
/* 填充:5 层叠放,当前档淡入、其余淡出 —— 任何颜色切换都是平滑 crossfade */
|
||||
.fill-stack {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 23px;
|
||||
border-radius: 17px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.fill-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s ease;
|
||||
}
|
||||
.fill-layer.on { opacity: 1; }
|
||||
.fill-layer.lv-0 { background: #f5c542; }
|
||||
.fill-layer.lv-1 { background: #3fd07c; }
|
||||
.fill-layer.lv-2 { background: #4f8ef7; }
|
||||
.fill-layer.lv-3 { background: #a55df0; }
|
||||
|
||||
/* xhigh:白光扫过(90deg 整列渐变无硬边;位移动画两端完全出画后回绕 → 无缝) */
|
||||
.fill-layer.lv-3::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -50%;
|
||||
width: 45%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.55) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
animation: shine-sweep 1.8s linear infinite;
|
||||
}
|
||||
@keyframes shine-sweep {
|
||||
from { left: -50%; }
|
||||
to { left: 130%; }
|
||||
}
|
||||
|
||||
/* max:彩虹流动(200% 宽 + 位移整数个循环单元 → 真无缝) */
|
||||
.fill-layer.lv-4 {
|
||||
width: 200%;
|
||||
background: repeating-linear-gradient(
|
||||
90deg,
|
||||
#ff3b5c 0%,
|
||||
#ff9f2e 7.15%,
|
||||
#ffe93b 14.3%,
|
||||
#3ddc68 21.45%,
|
||||
#2fd4ff 28.6%,
|
||||
#7a5cff 35.75%,
|
||||
#ff4fd8 42.9%,
|
||||
#ff3b5c 50%
|
||||
);
|
||||
animation: rainbow-flow 2.4s linear infinite;
|
||||
}
|
||||
/* 位移方向:色块随条从左向右流动;位移为整数个循环单元 → 真无缝 */
|
||||
@keyframes rainbow-flow {
|
||||
from { transform: translateX(-50%); }
|
||||
to { transform: translateX(0); }
|
||||
}
|
||||
|
||||
/* 圆钮与填充:transition 常驻 —— 点击也平滑滑动;仅真实拖动时关闭跟随 */
|
||||
.knob, .fill-stack {
|
||||
transition: left 0.2s ease, width 0.2s ease;
|
||||
}
|
||||
.effort-card.live-drag .knob,
|
||||
.effort-card.live-drag .fill-stack {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
/* 档位圆点 */
|
||||
.dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--dot-off);
|
||||
transition: background 0.2s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dot.covered { background: var(--dot-on); }
|
||||
|
||||
/* 拖柄 */
|
||||
.knob {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 50%;
|
||||
background: var(--knob-bg);
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
|
||||
cursor: grab;
|
||||
z-index: 2;
|
||||
}
|
||||
.effort-card.live-drag .knob { cursor: grabbing; }
|
||||
|
||||
/* ========== 档位文字 ========== */
|
||||
.level-labels {
|
||||
position: relative;
|
||||
height: 20px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.level-label {
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
transition: color 0.2s ease, font-weight 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.level-label.active {
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* demo 辅助:当前状态输出 */
|
||||
.demo-state {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-family: ui-monospace, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="demo-title">推理强度选择器 Demo · 拖动滑块 / 点击档位 / 勾选「默认」试试</div>
|
||||
|
||||
<div class="effort-card" id="card">
|
||||
<!-- 顶部:非拖动 = 推理强度 + 默认;拖动中 = 更高效 / 更智能 -->
|
||||
<div class="effort-header">
|
||||
<div class="header-state header-idle">
|
||||
<span class="header-title">推理强度</span>
|
||||
<span class="default-toggle checked" id="defaultToggle">
|
||||
<span class="default-label">默认</span>
|
||||
<span class="fancy-check" aria-hidden="true">
|
||||
<svg viewBox="0 0 64 64">
|
||||
<path
|
||||
d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
|
||||
pathLength="575.0541381835938"
|
||||
class="fancy-path"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="header-state header-drag">
|
||||
<span>更高效</span>
|
||||
<span>更智能</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 滑块 -->
|
||||
<div class="slider-zone">
|
||||
<div class="track" id="track">
|
||||
<div class="fill-stack" id="fillStack">
|
||||
<div class="fill-layer lv-0"></div>
|
||||
<div class="fill-layer lv-1"></div>
|
||||
<div class="fill-layer lv-2"></div>
|
||||
<div class="fill-layer lv-3"></div>
|
||||
<div class="fill-layer lv-4"></div>
|
||||
</div>
|
||||
<!-- dots 由 JS 生成 -->
|
||||
<div class="knob" id="knob"></div>
|
||||
</div>
|
||||
<div class="level-labels" id="labels"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-state" id="stateOut"></div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
||||
// 轨道内两端档位的圆钮中心 inset;比圆钮半径(23)小 3px,
|
||||
// 让圆钮在两端时外凸 3px,完整盖住填充条端头(避免相切处露出杂边)
|
||||
const EDGE = 20;
|
||||
|
||||
const card = document.getElementById('card');
|
||||
const track = document.getElementById('track');
|
||||
const fillStack = document.getElementById('fillStack');
|
||||
const layers = Array.from(fillStack.querySelectorAll('.fill-layer'));
|
||||
const knob = document.getElementById('knob');
|
||||
const labelsBox = document.getElementById('labels');
|
||||
const defaultToggle = document.getElementById('defaultToggle');
|
||||
const stateOut = document.getElementById('stateOut');
|
||||
|
||||
let levelIndex = 2; // 当前档位(记忆值,勾选默认时也保留)
|
||||
let isDefault = true; // 是否勾选「默认」
|
||||
let dragging = false; // 按下
|
||||
let liveDrag = false; // 按下且已移动(真实拖动中)
|
||||
let downX = 0;
|
||||
|
||||
const dots = [];
|
||||
const labelEls = [];
|
||||
|
||||
// 生成 5 个档位点 + 5 个文字标签
|
||||
LEVELS.forEach((name) => {
|
||||
const dot = document.createElement('div');
|
||||
dot.className = 'dot';
|
||||
track.insertBefore(dot, knob);
|
||||
dots.push(dot);
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'level-label';
|
||||
label.textContent = name;
|
||||
label.style.left = posPct(dots.length - 1);
|
||||
labelsBox.appendChild(label);
|
||||
labelEls.push(label);
|
||||
});
|
||||
|
||||
// 档位 i 在轨道内的位置(百分比字符串,供标签/圆点用)
|
||||
function posPct(i) {
|
||||
const ratio = i / (LEVELS.length - 1);
|
||||
return `calc(${EDGE}px + (100% - ${EDGE * 2}px) * ${ratio})`;
|
||||
}
|
||||
|
||||
// 档位 i 在轨道内的像素 x
|
||||
function posPx(i) {
|
||||
const w = track.clientWidth;
|
||||
return EDGE + (w - EDGE * 2) * (i / (LEVELS.length - 1));
|
||||
}
|
||||
|
||||
// 像素 x → 最近档位
|
||||
function pxToLevel(x) {
|
||||
const w = track.clientWidth;
|
||||
const ratio = (x - EDGE) / (w - EDGE * 2);
|
||||
return Math.max(0, Math.min(LEVELS.length - 1, Math.round(ratio * (LEVELS.length - 1))));
|
||||
}
|
||||
|
||||
function render(previewX) {
|
||||
// 拖动时用连续像素位置;否则吸附到档位点(CSS transition 负责平滑)
|
||||
const x = typeof previewX === 'number' ? previewX : posPx(levelIndex);
|
||||
const activeLv = typeof previewX === 'number' ? pxToLevel(previewX) : levelIndex;
|
||||
|
||||
fillStack.style.width = x + 'px';
|
||||
knob.style.left = x + 'px';
|
||||
layers.forEach((layer, i) => layer.classList.toggle('on', i === activeLv));
|
||||
|
||||
dots.forEach((dot, i) => {
|
||||
dot.style.left = posPct(i);
|
||||
dot.classList.toggle('covered', posPx(i) <= x);
|
||||
});
|
||||
labelEls.forEach((el, i) => el.classList.toggle('active', i === activeLv));
|
||||
|
||||
card.classList.toggle('is-default', isDefault);
|
||||
card.classList.toggle('live-drag', liveDrag);
|
||||
defaultToggle.classList.toggle('checked', isDefault);
|
||||
|
||||
stateOut.textContent =
|
||||
`level = ${isDefault ? '(默认,不传参)' : LEVELS[levelIndex]} | dragging = ${liveDrag}`;
|
||||
}
|
||||
|
||||
// ---- 交互 ----
|
||||
function onDown(e) {
|
||||
dragging = true;
|
||||
liveDrag = false;
|
||||
downX = e.clientX;
|
||||
track.setPointerCapture(e.pointerId);
|
||||
// 点击(含置灰状态):解除默认并平滑滑到目标档(transition 生效)
|
||||
if (isDefault) isDefault = false;
|
||||
levelIndex = pxToLevel(clampX(e));
|
||||
render();
|
||||
}
|
||||
|
||||
function onMove(e) {
|
||||
if (!dragging) return;
|
||||
if (!liveDrag && Math.abs(e.clientX - downX) > 3) liveDrag = true;
|
||||
if (liveDrag) render(clampX(e)); // 真实拖动:无 transition 连续跟随
|
||||
}
|
||||
|
||||
function onUp(e) {
|
||||
if (!dragging) return;
|
||||
dragging = false;
|
||||
if (liveDrag) {
|
||||
// 拖动结束:吸附最近档。先用松手点的连续位置渲染(knob 不动),
|
||||
// 同时移除 live-drag 恢复 transition;下一帧再吸附到档位点,
|
||||
// 避免 transition 恢复与位置变化同帧导致瞬间跳变
|
||||
const x = clampX(e);
|
||||
levelIndex = pxToLevel(x);
|
||||
liveDrag = false;
|
||||
render(x);
|
||||
requestAnimationFrame(() => render());
|
||||
return;
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
function clampX(e) {
|
||||
const rect = track.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
return Math.max(EDGE, Math.min(rect.width - EDGE, x));
|
||||
}
|
||||
|
||||
track.addEventListener('pointerdown', onDown);
|
||||
track.addEventListener('pointermove', onMove);
|
||||
track.addEventListener('pointerup', onUp);
|
||||
track.addEventListener('pointercancel', onUp);
|
||||
|
||||
// 勾选/取消「默认」
|
||||
defaultToggle.addEventListener('click', () => {
|
||||
isDefault = !isDefault;
|
||||
render();
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => render());
|
||||
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -8,14 +8,14 @@ from pathlib import Path
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
try:
|
||||
from config.limits import THINKING_FAST_INTERVAL
|
||||
from config.limits import REASONING_EFFORT_LEVELS
|
||||
except ImportError:
|
||||
THINKING_FAST_INTERVAL = 10
|
||||
REASONING_EFFORT_LEVELS = ("low", "medium", "high", "xhigh", "max")
|
||||
|
||||
from core.tool_config import TOOL_CATEGORIES
|
||||
from config.model_profiles import get_default_model_key, get_registered_model_keys
|
||||
|
||||
ALLOWED_RUN_MODES = {"fast", "thinking", "deep"}
|
||||
ALLOWED_RUN_MODES = {"fast", "thinking"}
|
||||
ALLOWED_PERMISSION_MODES = {"readonly", "approval", "auto_approval", "unrestricted"}
|
||||
ALLOWED_THEMES = {"classic", "light", "dark"}
|
||||
ALLOWED_COMMUNICATION_STYLES = {"default", "human_like", "auto"}
|
||||
@ -34,8 +34,6 @@ MAX_SHORT_FIELD_LENGTH = 20
|
||||
MAX_CONSIDERATION_TEXT_LENGTH = 2000
|
||||
MAX_CONSIDERATION_ITEMS = 10
|
||||
TONE_PRESETS = ["健谈", "幽默", "直言不讳", "鼓励性", "诗意", "企业商务", "打破常规", "同理心"]
|
||||
THINKING_INTERVAL_MIN = 1
|
||||
THINKING_INTERVAL_MAX = 50
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN = 1
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX = 30
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_DEFAULT = 10
|
||||
@ -66,11 +64,11 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
|
||||
"profession": "",
|
||||
"tone": "",
|
||||
"considerations": [],
|
||||
"thinking_interval": None,
|
||||
"disabled_tool_categories": [],
|
||||
"enabled_skills": None,
|
||||
"skills_catalog_snapshot": None,
|
||||
"default_run_mode": "deep",
|
||||
"default_run_mode": "thinking",
|
||||
"default_reasoning_effort": None, # 默认推理强度:None=默认(不传参)
|
||||
"default_permission_mode": "unrestricted",
|
||||
"auto_generate_title": True,
|
||||
"recent_conversations_prompt_enabled": False,
|
||||
@ -254,11 +252,6 @@ def sanitize_personalization_payload(
|
||||
else:
|
||||
base["considerations"] = _sanitize_considerations(base.get("considerations", []))
|
||||
|
||||
if "thinking_interval" in data:
|
||||
base["thinking_interval"] = _sanitize_thinking_interval(data.get("thinking_interval"))
|
||||
else:
|
||||
base["thinking_interval"] = _sanitize_thinking_interval(base.get("thinking_interval"))
|
||||
|
||||
# 工具意图提示开关
|
||||
if "tool_intent_enabled" in data:
|
||||
base["tool_intent_enabled"] = bool(data.get("tool_intent_enabled"))
|
||||
@ -316,6 +309,11 @@ def sanitize_personalization_payload(
|
||||
else:
|
||||
base["default_run_mode"] = _sanitize_run_mode(base.get("default_run_mode"))
|
||||
|
||||
if "default_reasoning_effort" in data:
|
||||
base["default_reasoning_effort"] = _sanitize_reasoning_effort(data.get("default_reasoning_effort"))
|
||||
else:
|
||||
base["default_reasoning_effort"] = _sanitize_reasoning_effort(base.get("default_reasoning_effort"))
|
||||
|
||||
permission_mode = data.get("default_permission_mode", base.get("default_permission_mode"))
|
||||
if isinstance(permission_mode, str) and permission_mode in ALLOWED_PERMISSION_MODES:
|
||||
base["default_permission_mode"] = permission_mode
|
||||
@ -844,19 +842,6 @@ def _sanitize_considerations(value: Any) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def _sanitize_thinking_interval(value: Any) -> Optional[int]:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
try:
|
||||
interval = int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
interval = max(THINKING_INTERVAL_MIN, min(THINKING_INTERVAL_MAX, interval))
|
||||
if interval == THINKING_FAST_INTERVAL:
|
||||
return None
|
||||
return interval
|
||||
|
||||
|
||||
def _sanitize_tool_categories(value: Any, allowed: set) -> list:
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
@ -881,6 +866,16 @@ def _sanitize_run_mode(value: Any) -> Optional[str]:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
candidate = value.strip().lower()
|
||||
if candidate == "deep": # 旧版标识符,映射为思考模式
|
||||
candidate = "thinking"
|
||||
if candidate in ALLOWED_RUN_MODES:
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _sanitize_reasoning_effort(value: Any) -> Optional[str]:
|
||||
"""推理强度:合法档位原样返回,其余(含空)一律 None=默认不传参。"""
|
||||
if not isinstance(value, str):
|
||||
return None
|
||||
candidate = value.strip().lower()
|
||||
return candidate if candidate in REASONING_EFFORT_LEVELS else None
|
||||
|
||||
@ -542,9 +542,6 @@ class SubAgentTask:
|
||||
client = DeepSeekClient(thinking_mode=(self.thinking_mode == "thinking"), web_mode=True)
|
||||
client.model_key = chosen_key
|
||||
client.project_path = str(self.manager.project_path)
|
||||
if self.thinking_mode == "thinking":
|
||||
# 子智能体的 thinking 模式应全程使用思考模型
|
||||
client.deep_thinking_session = True
|
||||
client.apply_profile(model_map[chosen_key])
|
||||
return client, chosen_key
|
||||
|
||||
|
||||
@ -277,9 +277,13 @@ def send_message_api(workspace_id: str):
|
||||
"personalization_name": personalization_name,
|
||||
"workspace_id": ws.workspace_id,
|
||||
})
|
||||
# 立即应用个性化偏好(确保禁用工具分类生效)
|
||||
# 立即应用个性化偏好(确保禁用工具分类生效);
|
||||
# 对话加载链路不应用默认 模型/模式/推理强度(以对话 meta 为权威)
|
||||
try:
|
||||
terminal.apply_personalization_preferences(terminal.context_manager.custom_personalization_config)
|
||||
terminal.apply_personalization_preferences(
|
||||
terminal.context_manager.custom_personalization_config,
|
||||
apply_default_modes=False,
|
||||
)
|
||||
except Exception as exc:
|
||||
debug_log(f"[api_v1] 应用个性化失败: {exc}")
|
||||
except Exception as exc:
|
||||
@ -688,7 +692,8 @@ def list_models_api():
|
||||
"visible": not bool(profile.get("hidden")),
|
||||
"supports_thinking": profile.get("supports_thinking", False),
|
||||
"fast_only": profile.get("fast_only", False),
|
||||
"deep_only": profile.get("deep_only", False),
|
||||
"thinking_only": profile.get("thinking_only", False),
|
||||
"supports_reasoning_effort": profile.get("supports_reasoning_effort", False),
|
||||
"multimodal": multimodal,
|
||||
"context_window": profile.get("context_window"),
|
||||
"max_output_tokens": (profile.get("fast") or {}).get("max_tokens"),
|
||||
|
||||
@ -225,7 +225,6 @@ from config import (
|
||||
DEFAULT_PROJECT_PATH,
|
||||
LOGS_DIR,
|
||||
AGENT_VERSION,
|
||||
THINKING_FAST_INTERVAL,
|
||||
MAX_ACTIVE_USER_CONTAINERS,
|
||||
PROJECT_MAX_STORAGE_MB,
|
||||
PROJECT_MAX_STORAGE_BYTES,
|
||||
@ -239,8 +238,6 @@ from modules.upload_security import UploadQuarantineManager, UploadSecurityError
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
)
|
||||
from modules.user_container_manager import UserContainerManager
|
||||
from modules.usage_tracker import UsageTracker, QUOTA_DEFAULTS
|
||||
@ -332,7 +329,6 @@ RECENT_UPLOAD_EVENT_LIMIT = 150
|
||||
RECENT_UPLOAD_FEED_LIMIT = 60
|
||||
|
||||
DEFAULT_PORT = WEB_SERVER_PORT
|
||||
THINKING_FAILURE_KEYWORDS = ["⚠️", "🛑", "失败", "错误", "异常", "终止", "error", "failed", "未完成", "超时", "强制"]
|
||||
CSRF_HEADER_NAME = state.CSRF_HEADER_NAME
|
||||
CSRF_SESSION_KEY = state.CSRF_SESSION_KEY
|
||||
CSRF_SAFE_METHODS = state.CSRF_SAFE_METHODS
|
||||
@ -1098,10 +1094,6 @@ def ensure_conversation_loaded(terminal: WebTerminal, conversation_id: Optional[
|
||||
terminal.set_run_mode("thinking")
|
||||
else:
|
||||
terminal.set_run_mode("fast")
|
||||
if terminal.thinking_mode:
|
||||
terminal.api_client.start_new_task(force_deep=terminal.deep_thinking_mode)
|
||||
else:
|
||||
terminal.api_client.start_new_task()
|
||||
session['run_mode'] = terminal.run_mode
|
||||
session['thinking_mode'] = terminal.thinking_mode
|
||||
except Exception:
|
||||
@ -1114,11 +1106,7 @@ def reset_system_state(terminal: Optional[WebTerminal]):
|
||||
return
|
||||
|
||||
try:
|
||||
# 1. 重置API客户端状态
|
||||
if hasattr(terminal, 'api_client') and terminal.api_client:
|
||||
debug_log("重置API客户端状态")
|
||||
terminal.api_client.start_new_task(force_deep=getattr(terminal, "deep_thinking_mode", False))
|
||||
|
||||
# 1. 重置API客户端状态(思考状态已简化,无需额外重置)
|
||||
# 2. 重置主终端会话状态
|
||||
if hasattr(terminal, 'current_session_id'):
|
||||
terminal.current_session_id += 1 # 开始新会话
|
||||
@ -1189,101 +1177,6 @@ def log_goal_mode_debug_entry(data: Dict[str, Any]):
|
||||
_write_log(GOAL_MODE_DEBUG_LOG_FILE, serialized)
|
||||
|
||||
|
||||
def get_thinking_state(terminal: WebTerminal) -> Dict[str, Any]:
|
||||
"""获取(或初始化)思考调度状态。"""
|
||||
state = getattr(terminal, "_thinking_state", None)
|
||||
if not state:
|
||||
state = {"fast_streak": 0, "force_next": False, "suppress_next": False}
|
||||
terminal._thinking_state = state
|
||||
return state
|
||||
|
||||
|
||||
def mark_force_thinking(terminal: WebTerminal, reason: str = ""):
|
||||
"""标记下一次API调用必须使用思考模型。"""
|
||||
if getattr(terminal, "deep_thinking_mode", False):
|
||||
return
|
||||
if not getattr(terminal, "thinking_mode", False):
|
||||
return
|
||||
state = get_thinking_state(terminal)
|
||||
state["force_next"] = True
|
||||
if reason:
|
||||
debug_log(f"[Thinking] 下次强制思考,原因: {reason}")
|
||||
|
||||
|
||||
def mark_suppress_thinking(terminal: WebTerminal):
|
||||
"""标记下一次API调用必须跳过思考模型(例如写入窗口)。"""
|
||||
if getattr(terminal, "deep_thinking_mode", False):
|
||||
return
|
||||
if not getattr(terminal, "thinking_mode", False):
|
||||
return
|
||||
state = get_thinking_state(terminal)
|
||||
state["suppress_next"] = True
|
||||
|
||||
|
||||
def apply_thinking_schedule(terminal: WebTerminal):
|
||||
"""根据当前状态配置API客户端的思考/快速模式。"""
|
||||
client = terminal.api_client
|
||||
if getattr(terminal, "deep_thinking_mode", False):
|
||||
client.force_thinking_next_call = False
|
||||
client.skip_thinking_next_call = False
|
||||
return
|
||||
if not getattr(terminal, "thinking_mode", False):
|
||||
client.force_thinking_next_call = False
|
||||
client.skip_thinking_next_call = False
|
||||
return
|
||||
state = get_thinking_state(terminal)
|
||||
if state.get("suppress_next"):
|
||||
client.skip_thinking_next_call = True
|
||||
state["suppress_next"] = False
|
||||
debug_log("[Thinking] 已标记跳过思考模式。")
|
||||
return
|
||||
if state.get("force_next"):
|
||||
client.force_thinking_next_call = True
|
||||
state["force_next"] = False
|
||||
state["fast_streak"] = 0
|
||||
debug_log("[Thinking] 响应失败,下一次强制思考。")
|
||||
return
|
||||
custom_interval = getattr(terminal, "thinking_fast_interval", THINKING_FAST_INTERVAL)
|
||||
interval = max(0, custom_interval or 0)
|
||||
if interval > 0:
|
||||
allowed_fast = max(0, interval - 1)
|
||||
if state.get("fast_streak", 0) >= allowed_fast:
|
||||
client.force_thinking_next_call = True
|
||||
state["fast_streak"] = 0
|
||||
if allowed_fast == 0:
|
||||
debug_log("[Thinking] 频率=1,持续思考。")
|
||||
else:
|
||||
debug_log(f"[Thinking] 快速模式已连续 {allowed_fast} 次,下一次强制思考。")
|
||||
return
|
||||
client.force_thinking_next_call = False
|
||||
client.skip_thinking_next_call = False
|
||||
|
||||
|
||||
def update_thinking_after_call(terminal: WebTerminal):
|
||||
"""一次API调用完成后更新快速计数。"""
|
||||
if getattr(terminal, "deep_thinking_mode", False):
|
||||
state = get_thinking_state(terminal)
|
||||
state["fast_streak"] = 0
|
||||
return
|
||||
if not getattr(terminal, "thinking_mode", False):
|
||||
return
|
||||
state = get_thinking_state(terminal)
|
||||
if terminal.api_client.last_call_used_thinking:
|
||||
state["fast_streak"] = 0
|
||||
else:
|
||||
state["fast_streak"] = state.get("fast_streak", 0) + 1
|
||||
debug_log(f"[Thinking] 快速模式计数: {state['fast_streak']}")
|
||||
|
||||
|
||||
def maybe_mark_failure_from_message(terminal: WebTerminal, content: Optional[str]):
|
||||
"""根据system消息内容判断是否需要强制思考。"""
|
||||
if not content:
|
||||
return
|
||||
normalized = content.lower()
|
||||
if any(keyword.lower() in normalized for keyword in THINKING_FAILURE_KEYWORDS):
|
||||
mark_force_thinking(terminal, reason="system_message")
|
||||
|
||||
|
||||
def detect_tool_failure(result_data: Any) -> bool:
|
||||
"""识别工具返回结果是否代表失败。"""
|
||||
if not isinstance(result_data, dict):
|
||||
|
||||
@ -167,7 +167,9 @@ def login():
|
||||
candidate_mode = (personal_config or {}).get('default_run_mode')
|
||||
if isinstance(candidate_mode, str):
|
||||
normalized_mode = candidate_mode.lower()
|
||||
if normalized_mode in {"fast", "thinking", "deep"}:
|
||||
if normalized_mode == "deep": # 旧版标识符映射
|
||||
normalized_mode = "thinking"
|
||||
if normalized_mode in {"fast", "thinking"}:
|
||||
preferred_run_mode = normalized_mode
|
||||
except Exception as exc:
|
||||
debug_log(f"加载个性化偏好失败: {exc}")
|
||||
|
||||
@ -13,13 +13,11 @@ from werkzeug.utils import secure_filename
|
||||
from werkzeug.exceptions import RequestEntityTooLarge
|
||||
import secrets
|
||||
|
||||
from config import THINKING_FAST_INTERVAL, MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from config import MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
resolve_context_compression_settings,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||||
)
|
||||
@ -39,7 +37,7 @@ from server.auth_helpers import api_login_required, resolve_admin_policy, get_cu
|
||||
from server.context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker
|
||||
from server.security import rate_limited, prune_socket_tokens
|
||||
from server.utils_common import debug_log
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, THINKING_FAILURE_KEYWORDS, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import tool_approval_manager, user_question_manager
|
||||
from server.extensions import socketio
|
||||
from server.monitor import get_cached_monitor_snapshot
|
||||
|
||||
@ -14,13 +14,11 @@ from werkzeug.exceptions import RequestEntityTooLarge
|
||||
import secrets
|
||||
import mimetypes
|
||||
|
||||
from config import THINKING_FAST_INTERVAL, MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from config import MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
resolve_context_compression_settings,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||||
)
|
||||
@ -40,7 +38,7 @@ from server.auth_helpers import api_login_required, resolve_admin_policy, get_cu
|
||||
from server.context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker
|
||||
from server.security import rate_limited, prune_socket_tokens
|
||||
from server.utils_common import debug_log
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, THINKING_FAILURE_KEYWORDS, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import tool_approval_manager, user_question_manager
|
||||
from server.extensions import socketio
|
||||
from server.monitor import get_cached_monitor_snapshot
|
||||
|
||||
@ -13,13 +13,11 @@ from werkzeug.utils import secure_filename
|
||||
from werkzeug.exceptions import RequestEntityTooLarge
|
||||
import secrets
|
||||
|
||||
from config import THINKING_FAST_INTERVAL, MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from config import MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
resolve_context_compression_settings,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||||
)
|
||||
@ -39,7 +37,7 @@ from server.auth_helpers import api_login_required, resolve_admin_policy, get_cu
|
||||
from server.context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker
|
||||
from server.security import rate_limited, prune_socket_tokens
|
||||
from server.utils_common import debug_log
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, THINKING_FAILURE_KEYWORDS, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import tool_approval_manager, user_question_manager
|
||||
from server.extensions import socketio
|
||||
from server.monitor import get_cached_monitor_snapshot
|
||||
|
||||
@ -18,13 +18,11 @@ from werkzeug.utils import secure_filename
|
||||
from werkzeug.exceptions import RequestEntityTooLarge
|
||||
import secrets
|
||||
|
||||
from config import THINKING_FAST_INTERVAL, MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from config import MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
resolve_context_compression_settings,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||||
)
|
||||
@ -49,7 +47,7 @@ from server.auth_helpers import api_login_required, resolve_admin_policy, get_cu
|
||||
from server.context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker, get_user_resources
|
||||
from server.security import rate_limited, prune_socket_tokens
|
||||
from server.utils_common import debug_log
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, THINKING_FAILURE_KEYWORDS, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import tool_approval_manager, user_question_manager
|
||||
from server.extensions import socketio
|
||||
from server.monitor import get_cached_monitor_snapshot
|
||||
|
||||
@ -13,13 +13,11 @@ from werkzeug.utils import secure_filename
|
||||
from werkzeug.exceptions import RequestEntityTooLarge
|
||||
import secrets
|
||||
|
||||
from config import THINKING_FAST_INTERVAL, MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from config import MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
resolve_context_compression_settings,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||||
)
|
||||
@ -39,7 +37,7 @@ from server.auth_helpers import api_login_required, resolve_admin_policy, get_cu
|
||||
from server.context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker
|
||||
from server.security import rate_limited, prune_socket_tokens
|
||||
from server.utils_common import debug_log
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, THINKING_FAILURE_KEYWORDS, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import tool_approval_manager, user_question_manager
|
||||
from server.extensions import socketio
|
||||
from server.monitor import get_cached_monitor_snapshot
|
||||
@ -82,21 +80,19 @@ def _dispatch_runtime_mode_notice(terminal: WebTerminal, username: str, text: st
|
||||
@with_terminal
|
||||
@rate_limited("thinking_mode_toggle", 15, 60, scope="user")
|
||||
def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, username: str):
|
||||
"""切换思考模式"""
|
||||
"""切换运行模式(fast / thinking;旧值 deep 自动映射为 thinking)"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
requested_mode = data.get('mode')
|
||||
if requested_mode in {"fast", "thinking", "deep"}:
|
||||
if isinstance(requested_mode, str) and requested_mode.lower() == 'deep':
|
||||
requested_mode = 'thinking'
|
||||
if requested_mode in {"fast", "thinking"}:
|
||||
target_mode = requested_mode
|
||||
elif 'thinking_mode' in data:
|
||||
target_mode = "thinking" if bool(data.get('thinking_mode')) else "fast"
|
||||
else:
|
||||
target_mode = terminal.run_mode
|
||||
terminal.set_run_mode(target_mode)
|
||||
if terminal.thinking_mode:
|
||||
terminal.api_client.start_new_task(force_deep=terminal.deep_thinking_mode)
|
||||
else:
|
||||
terminal.api_client.start_new_task()
|
||||
session['thinking_mode'] = terminal.thinking_mode
|
||||
session['run_mode'] = terminal.run_mode
|
||||
# 更新当前对话的元数据
|
||||
@ -136,6 +132,72 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
|
||||
"message": "切换思考模式时发生异常"
|
||||
}), code
|
||||
|
||||
@chat_bp.route('/api/reasoning-effort', methods=['POST'])
|
||||
@api_login_required
|
||||
@with_terminal
|
||||
@rate_limited("reasoning_effort_toggle", 20, 60, scope="user")
|
||||
def update_reasoning_effort(terminal: WebTerminal, workspace: UserWorkspace, username: str):
|
||||
"""设置会话级推理强度;effort 为 null/空 表示默认(请求中不传该参数)。"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
effort = data.get('effort')
|
||||
if isinstance(effort, str) and not effort.strip():
|
||||
effort = None
|
||||
# 前端防抖序列开始时记录的所属对话:指定后把值写入该对话 meta;
|
||||
# terminal 当前状态仅当保存目标就是当前对话时才更新,避免串写
|
||||
ctx = terminal.context_manager
|
||||
target_conversation_id = str(data.get('conversation_id') or '').strip() or None
|
||||
is_current_target = not target_conversation_id or target_conversation_id == ctx.current_conversation_id
|
||||
if is_current_target:
|
||||
terminal.set_reasoning_effort(effort)
|
||||
save_conversation_id = target_conversation_id or ctx.current_conversation_id
|
||||
# 更新对话的元数据
|
||||
if save_conversation_id:
|
||||
try:
|
||||
if is_current_target:
|
||||
ctx.conversation_manager.save_conversation(
|
||||
conversation_id=save_conversation_id,
|
||||
messages=ctx.conversation_history,
|
||||
project_path=str(ctx.project_path),
|
||||
todo_list=ctx.todo_list,
|
||||
thinking_mode=terminal.thinking_mode,
|
||||
run_mode=terminal.run_mode,
|
||||
reasoning_effort=effort,
|
||||
model_key=getattr(terminal, "model_key", None),
|
||||
has_images=getattr(ctx, "has_images", False),
|
||||
has_videos=getattr(ctx, "has_videos", False)
|
||||
)
|
||||
else:
|
||||
# 所属对话已不是当前对话:只更新推理强度 meta。
|
||||
# 重新读出目标对话消息回传(merge 语义下保持不变),
|
||||
# 避免把当前对话的消息串写过去
|
||||
existing = ctx.conversation_manager.load_conversation(save_conversation_id) or {}
|
||||
ctx.conversation_manager.save_conversation(
|
||||
conversation_id=save_conversation_id,
|
||||
messages=existing.get("messages") or [],
|
||||
reasoning_effort=effort,
|
||||
)
|
||||
except Exception as exc:
|
||||
print(f"[API] 保存推理强度到对话失败: {exc}")
|
||||
|
||||
status = terminal.get_status()
|
||||
socketio.emit('status_update', status, room=f"user_{username}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"data": {
|
||||
"reasoning_effort": terminal.reasoning_effort
|
||||
}
|
||||
})
|
||||
except Exception as exc:
|
||||
print(f"[API] 设置推理强度失败: {exc}")
|
||||
code = 400 if isinstance(exc, ValueError) else 500
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(exc),
|
||||
"message": "设置推理强度时发生异常"
|
||||
}), code
|
||||
|
||||
@chat_bp.route('/api/model', methods=['POST'])
|
||||
@api_login_required
|
||||
@with_terminal
|
||||
@ -185,6 +247,7 @@ def update_model(terminal: WebTerminal, workspace: UserWorkspace, username: str)
|
||||
todo_list=ctx.todo_list,
|
||||
thinking_mode=terminal.thinking_mode,
|
||||
run_mode=terminal.run_mode,
|
||||
reasoning_effort=terminal.reasoning_effort,
|
||||
model_key=terminal.model_key,
|
||||
has_images=getattr(ctx, "has_images", False),
|
||||
has_videos=getattr(ctx, "has_videos", False)
|
||||
@ -249,11 +312,6 @@ def get_personalization_settings(terminal: WebTerminal, workspace: UserWorkspace
|
||||
int(model_window or compression_settings.get("deep_trigger_tokens") or 0),
|
||||
),
|
||||
},
|
||||
"thinking_interval_default": THINKING_FAST_INTERVAL,
|
||||
"thinking_interval_range": {
|
||||
"min": THINKING_INTERVAL_MIN,
|
||||
"max": THINKING_INTERVAL_MAX
|
||||
},
|
||||
"recent_conversations_prompt_limit_range": {
|
||||
"min": RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||||
"max": RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||||
@ -293,7 +351,12 @@ def update_personalization_settings(terminal: WebTerminal, workspace: UserWorksp
|
||||
except Exception as sync_exc:
|
||||
debug_log(f"[Skills] 同步失败: {sync_exc}")
|
||||
try:
|
||||
terminal.apply_personalization_preferences(config)
|
||||
# 对话级 terminal 的 模型/模式/推理强度 以对话 meta 为权威,
|
||||
# 保存个性化默认值不得回写当前对话;工作区级(/new)则立即生效
|
||||
terminal.apply_personalization_preferences(
|
||||
config,
|
||||
apply_default_modes=not bool(getattr(terminal, '_bound_conversation_id', None)),
|
||||
)
|
||||
session['run_mode'] = terminal.run_mode
|
||||
session['thinking_mode'] = terminal.thinking_mode
|
||||
ctx = getattr(terminal, 'context_manager', None)
|
||||
@ -335,11 +398,6 @@ def update_personalization_settings(terminal: WebTerminal, workspace: UserWorksp
|
||||
int(model_window or compression_settings.get("deep_trigger_tokens") or 0),
|
||||
),
|
||||
},
|
||||
"thinking_interval_default": THINKING_FAST_INTERVAL,
|
||||
"thinking_interval_range": {
|
||||
"min": THINKING_INTERVAL_MIN,
|
||||
"max": THINKING_INTERVAL_MAX
|
||||
},
|
||||
"recent_conversations_prompt_limit_range": {
|
||||
"min": RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||||
"max": RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||||
|
||||
@ -13,13 +13,11 @@ from werkzeug.utils import secure_filename
|
||||
from werkzeug.exceptions import RequestEntityTooLarge
|
||||
import secrets
|
||||
|
||||
from config import THINKING_FAST_INTERVAL, MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from config import MAX_UPLOAD_SIZE, OUTPUT_FORMATS
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
resolve_context_compression_settings,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
|
||||
RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
|
||||
)
|
||||
@ -39,7 +37,7 @@ from server.auth_helpers import api_login_required, resolve_admin_policy, get_cu
|
||||
from server.context import with_terminal, get_gui_manager, get_upload_guard, build_upload_error_response, ensure_conversation_loaded, get_or_create_usage_tracker
|
||||
from server.security import rate_limited, prune_socket_tokens
|
||||
from server.utils_common import debug_log
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, THINKING_FAILURE_KEYWORDS, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import PROJECT_MAX_STORAGE_MB, pending_socket_tokens, SOCKET_TOKEN_TTL_SECONDS
|
||||
from server.state import tool_approval_manager, user_question_manager
|
||||
from server.extensions import socketio
|
||||
from server.monitor import get_cached_monitor_snapshot
|
||||
|
||||
@ -35,7 +35,6 @@ from config import (
|
||||
DEFAULT_PROJECT_PATH,
|
||||
LOGS_DIR,
|
||||
AGENT_VERSION,
|
||||
THINKING_FAST_INTERVAL,
|
||||
PROJECT_MAX_STORAGE_MB,
|
||||
PROJECT_MAX_STORAGE_BYTES,
|
||||
UPLOAD_SCAN_LOG_SUBDIR,
|
||||
@ -43,8 +42,6 @@ from config import (
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
)
|
||||
from modules.sub_agent.state import TERMINAL_STATUSES as SUB_AGENT_TERMINAL_STATUSES
|
||||
from modules.upload_security import UploadSecurityError
|
||||
@ -88,7 +85,6 @@ from .state import (
|
||||
PROJECT_STORAGE_CACHE_TTL_SECONDS,
|
||||
RECENT_UPLOAD_EVENT_LIMIT,
|
||||
RECENT_UPLOAD_FEED_LIMIT,
|
||||
THINKING_FAILURE_KEYWORDS,
|
||||
TITLE_PROMPT_PATH,
|
||||
get_last_active_ts,
|
||||
user_manager,
|
||||
@ -105,12 +101,6 @@ from .state import (
|
||||
from .chat_flow_helpers import (
|
||||
detect_malformed_tool_call as _detect_malformed_tool_call,
|
||||
detect_tool_failure,
|
||||
get_thinking_state,
|
||||
mark_force_thinking as _mark_force_thinking,
|
||||
mark_suppress_thinking,
|
||||
apply_thinking_schedule as _apply_thinking_schedule,
|
||||
update_thinking_after_call as _update_thinking_after_call,
|
||||
maybe_mark_failure_from_message as _maybe_mark_failure_from_message,
|
||||
generate_conversation_title_background as _generate_conversation_title_background,
|
||||
)
|
||||
from .chat_flow_runner import handle_task_with_sender
|
||||
@ -132,27 +122,6 @@ def generate_conversation_title_background(web_terminal: WebTerminal, conversati
|
||||
)
|
||||
|
||||
|
||||
def mark_force_thinking(terminal: WebTerminal, reason: str = ""):
|
||||
return _mark_force_thinking(terminal, reason=reason, debug_logger=debug_log)
|
||||
|
||||
|
||||
def apply_thinking_schedule(terminal: WebTerminal):
|
||||
return _apply_thinking_schedule(terminal, default_interval=THINKING_FAST_INTERVAL, debug_logger=debug_log)
|
||||
|
||||
|
||||
def update_thinking_after_call(terminal: WebTerminal):
|
||||
return _update_thinking_after_call(terminal, debug_logger=debug_log)
|
||||
|
||||
|
||||
def maybe_mark_failure_from_message(terminal: WebTerminal, content: Optional[str]):
|
||||
return _maybe_mark_failure_from_message(
|
||||
terminal,
|
||||
content,
|
||||
failure_keywords=THINKING_FAILURE_KEYWORDS,
|
||||
debug_logger=debug_log,
|
||||
)
|
||||
|
||||
|
||||
def detect_malformed_tool_call(text):
|
||||
return _detect_malformed_tool_call(text)
|
||||
|
||||
|
||||
@ -183,110 +183,6 @@ def generate_conversation_title_background(
|
||||
_title_debug_log("title_background_runner_exception", error=str(exc), conversation_id=conversation_id, username=username)
|
||||
|
||||
|
||||
def get_thinking_state(terminal: WebTerminal) -> Dict[str, Any]:
|
||||
"""获取(或初始化)思考调度状态。"""
|
||||
state = getattr(terminal, "_thinking_state", None)
|
||||
if not state:
|
||||
state = {"fast_streak": 0, "force_next": False, "suppress_next": False}
|
||||
terminal._thinking_state = state
|
||||
return state
|
||||
|
||||
|
||||
def mark_force_thinking(terminal: WebTerminal, reason: str = "", debug_logger=None):
|
||||
"""标记下一次API调用必须使用思考模型。"""
|
||||
if getattr(terminal, "deep_thinking_mode", False):
|
||||
return
|
||||
if not getattr(terminal, "thinking_mode", False):
|
||||
return
|
||||
state = get_thinking_state(terminal)
|
||||
state["force_next"] = True
|
||||
if reason and callable(debug_logger):
|
||||
debug_logger(f"[Thinking] 下次强制思考,原因: {reason}")
|
||||
|
||||
|
||||
def mark_suppress_thinking(terminal: WebTerminal):
|
||||
"""标记下一次API调用必须跳过思考模型(例如写入窗口)。"""
|
||||
if getattr(terminal, "deep_thinking_mode", False):
|
||||
return
|
||||
if not getattr(terminal, "thinking_mode", False):
|
||||
return
|
||||
state = get_thinking_state(terminal)
|
||||
state["suppress_next"] = True
|
||||
|
||||
|
||||
def apply_thinking_schedule(terminal: WebTerminal, default_interval: int, debug_logger):
|
||||
"""根据当前状态配置API客户端的思考/快速模式。"""
|
||||
client = terminal.api_client
|
||||
if getattr(terminal, "deep_thinking_mode", False):
|
||||
client.force_thinking_next_call = False
|
||||
client.skip_thinking_next_call = False
|
||||
return
|
||||
if not getattr(terminal, "thinking_mode", False):
|
||||
client.force_thinking_next_call = False
|
||||
client.skip_thinking_next_call = False
|
||||
return
|
||||
|
||||
state = get_thinking_state(terminal)
|
||||
if state.get("suppress_next"):
|
||||
client.skip_thinking_next_call = True
|
||||
state["suppress_next"] = False
|
||||
debug_logger("[Thinking] 已标记跳过思考模式。")
|
||||
return
|
||||
if state.get("force_next"):
|
||||
client.force_thinking_next_call = True
|
||||
state["force_next"] = False
|
||||
state["fast_streak"] = 0
|
||||
debug_logger("[Thinking] 响应失败,下一次强制思考。")
|
||||
return
|
||||
|
||||
custom_interval = getattr(terminal, "thinking_fast_interval", default_interval)
|
||||
interval = max(0, custom_interval or 0)
|
||||
if interval > 0:
|
||||
allowed_fast = max(0, interval - 1)
|
||||
if state.get("fast_streak", 0) >= allowed_fast:
|
||||
client.force_thinking_next_call = True
|
||||
state["fast_streak"] = 0
|
||||
if allowed_fast == 0:
|
||||
debug_logger("[Thinking] 频率=1,持续思考。")
|
||||
else:
|
||||
debug_logger(f"[Thinking] 快速模式已连续 {allowed_fast} 次,下一次强制思考。")
|
||||
return
|
||||
|
||||
client.force_thinking_next_call = False
|
||||
client.skip_thinking_next_call = False
|
||||
|
||||
|
||||
def update_thinking_after_call(terminal: WebTerminal, debug_logger):
|
||||
"""一次API调用完成后更新快速计数。"""
|
||||
if getattr(terminal, "deep_thinking_mode", False):
|
||||
state = get_thinking_state(terminal)
|
||||
state["fast_streak"] = 0
|
||||
return
|
||||
if not getattr(terminal, "thinking_mode", False):
|
||||
return
|
||||
|
||||
state = get_thinking_state(terminal)
|
||||
if terminal.api_client.last_call_used_thinking:
|
||||
state["fast_streak"] = 0
|
||||
else:
|
||||
state["fast_streak"] = state.get("fast_streak", 0) + 1
|
||||
debug_logger(f"[Thinking] 快速模式计数: {state['fast_streak']}")
|
||||
|
||||
|
||||
def maybe_mark_failure_from_message(
|
||||
terminal: WebTerminal,
|
||||
content: Optional[str],
|
||||
failure_keywords,
|
||||
debug_logger,
|
||||
):
|
||||
"""根据system消息内容判断是否需要强制思考。"""
|
||||
if not content:
|
||||
return
|
||||
normalized = content.lower()
|
||||
if any(keyword.lower() in normalized for keyword in failure_keywords):
|
||||
mark_force_thinking(terminal, reason="system_message", debug_logger=debug_logger)
|
||||
|
||||
|
||||
def detect_tool_failure(result_data: Any) -> bool:
|
||||
"""识别工具返回结果是否代表失败。"""
|
||||
if not isinstance(result_data, dict):
|
||||
|
||||
@ -2,20 +2,12 @@ from __future__ import annotations
|
||||
|
||||
from .chat_flow_runtime import (
|
||||
generate_conversation_title_background,
|
||||
mark_force_thinking,
|
||||
apply_thinking_schedule,
|
||||
update_thinking_after_call,
|
||||
maybe_mark_failure_from_message,
|
||||
detect_malformed_tool_call,
|
||||
)
|
||||
from .chat_flow_task_runner import handle_task_with_sender
|
||||
|
||||
__all__ = [
|
||||
"generate_conversation_title_background",
|
||||
"mark_force_thinking",
|
||||
"apply_thinking_schedule",
|
||||
"update_thinking_after_call",
|
||||
"maybe_mark_failure_from_message",
|
||||
"detect_malformed_tool_call",
|
||||
"handle_task_with_sender",
|
||||
]
|
||||
|
||||
@ -28,7 +28,6 @@ from config import (
|
||||
DEFAULT_PROJECT_PATH,
|
||||
LOGS_DIR,
|
||||
AGENT_VERSION,
|
||||
THINKING_FAST_INTERVAL,
|
||||
PROJECT_MAX_STORAGE_MB,
|
||||
PROJECT_MAX_STORAGE_BYTES,
|
||||
UPLOAD_SCAN_LOG_SUBDIR,
|
||||
@ -36,8 +35,6 @@ from config import (
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
)
|
||||
from modules.upload_security import UploadSecurityError
|
||||
from modules.user_manager import UserWorkspace
|
||||
@ -79,7 +76,6 @@ from .state import (
|
||||
PROJECT_STORAGE_CACHE_TTL_SECONDS,
|
||||
RECENT_UPLOAD_EVENT_LIMIT,
|
||||
RECENT_UPLOAD_FEED_LIMIT,
|
||||
THINKING_FAILURE_KEYWORDS,
|
||||
TITLE_PROMPT_PATH,
|
||||
get_last_active_ts,
|
||||
user_manager,
|
||||
@ -96,12 +92,6 @@ from .state import (
|
||||
from .chat_flow_helpers import (
|
||||
detect_malformed_tool_call as _detect_malformed_tool_call,
|
||||
detect_tool_failure,
|
||||
get_thinking_state,
|
||||
mark_force_thinking as _mark_force_thinking,
|
||||
mark_suppress_thinking,
|
||||
apply_thinking_schedule as _apply_thinking_schedule,
|
||||
update_thinking_after_call as _update_thinking_after_call,
|
||||
maybe_mark_failure_from_message as _maybe_mark_failure_from_message,
|
||||
generate_conversation_title_background as _generate_conversation_title_background,
|
||||
)
|
||||
|
||||
@ -125,22 +115,5 @@ def generate_conversation_title_background(web_terminal: WebTerminal, conversati
|
||||
debug_logger=debug_log,
|
||||
)
|
||||
|
||||
def mark_force_thinking(terminal: WebTerminal, reason: str = ""):
|
||||
return _mark_force_thinking(terminal, reason=reason, debug_logger=debug_log)
|
||||
|
||||
def apply_thinking_schedule(terminal: WebTerminal):
|
||||
return _apply_thinking_schedule(terminal, default_interval=THINKING_FAST_INTERVAL, debug_logger=debug_log)
|
||||
|
||||
def update_thinking_after_call(terminal: WebTerminal):
|
||||
return _update_thinking_after_call(terminal, debug_logger=debug_log)
|
||||
|
||||
def maybe_mark_failure_from_message(terminal: WebTerminal, content: Optional[str]):
|
||||
return _maybe_mark_failure_from_message(
|
||||
terminal,
|
||||
content,
|
||||
failure_keywords=THINKING_FAILURE_KEYWORDS,
|
||||
debug_logger=debug_log,
|
||||
)
|
||||
|
||||
def detect_malformed_tool_call(text):
|
||||
return _detect_malformed_tool_call(text)
|
||||
|
||||
@ -28,7 +28,6 @@ from config import (
|
||||
DEFAULT_PROJECT_PATH,
|
||||
LOGS_DIR,
|
||||
AGENT_VERSION,
|
||||
THINKING_FAST_INTERVAL,
|
||||
PROJECT_MAX_STORAGE_MB,
|
||||
PROJECT_MAX_STORAGE_BYTES,
|
||||
UPLOAD_SCAN_LOG_SUBDIR,
|
||||
@ -36,8 +35,6 @@ from config import (
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
resolve_context_compression_settings,
|
||||
)
|
||||
from modules.skill_hint_manager import SkillHintManager
|
||||
@ -86,7 +83,6 @@ from .state import (
|
||||
PROJECT_STORAGE_CACHE_TTL_SECONDS,
|
||||
RECENT_UPLOAD_EVENT_LIMIT,
|
||||
RECENT_UPLOAD_FEED_LIMIT,
|
||||
THINKING_FAILURE_KEYWORDS,
|
||||
TITLE_PROMPT_PATH,
|
||||
get_last_active_ts,
|
||||
user_manager,
|
||||
@ -104,12 +100,6 @@ from .state import (
|
||||
from .chat_flow_helpers import (
|
||||
detect_malformed_tool_call as _detect_malformed_tool_call,
|
||||
detect_tool_failure,
|
||||
get_thinking_state,
|
||||
mark_force_thinking as _mark_force_thinking,
|
||||
mark_suppress_thinking,
|
||||
apply_thinking_schedule as _apply_thinking_schedule,
|
||||
update_thinking_after_call as _update_thinking_after_call,
|
||||
maybe_mark_failure_from_message as _maybe_mark_failure_from_message,
|
||||
generate_conversation_title_background as _generate_conversation_title_background,
|
||||
)
|
||||
|
||||
@ -124,10 +114,6 @@ from .chat_flow_runner_helpers import (
|
||||
|
||||
from .chat_flow_runtime import (
|
||||
generate_conversation_title_background,
|
||||
mark_force_thinking,
|
||||
apply_thinking_schedule,
|
||||
update_thinking_after_call,
|
||||
maybe_mark_failure_from_message,
|
||||
detect_malformed_tool_call,
|
||||
)
|
||||
|
||||
@ -1390,14 +1376,6 @@ async def handle_task_with_sender(
|
||||
|
||||
raw_sender(event_type, payload)
|
||||
|
||||
# 如果是思考模式,重置状态
|
||||
if web_terminal.thinking_mode:
|
||||
web_terminal.api_client.start_new_task(force_deep=web_terminal.deep_thinking_mode)
|
||||
state = get_thinking_state(web_terminal)
|
||||
state["fast_streak"] = 0
|
||||
state["force_next"] = False
|
||||
state["suppress_next"] = False
|
||||
|
||||
# 添加到对话历史
|
||||
user_work_started_at = datetime.now().isoformat()
|
||||
user_message_index = -1
|
||||
@ -1885,11 +1863,8 @@ async def handle_task_with_sender(
|
||||
sender('system_message', {
|
||||
'content': f'⚠️ 已达到最大工具调用次数限制 ({MAX_TOTAL_TOOL_CALLS}),任务结束。'
|
||||
})
|
||||
mark_force_thinking(web_terminal, reason="tool_limit")
|
||||
break
|
||||
|
||||
apply_thinking_schedule(web_terminal)
|
||||
|
||||
full_response = ""
|
||||
tool_calls = []
|
||||
current_thinking = ""
|
||||
@ -2025,12 +2000,6 @@ async def handle_task_with_sender(
|
||||
if full_response.strip():
|
||||
debug_log(f"流式文本内容长度: {len(full_response)} 字符")
|
||||
|
||||
if web_terminal.api_client.last_call_used_thinking and current_thinking:
|
||||
web_terminal.api_client.current_task_thinking = current_thinking or ""
|
||||
if web_terminal.api_client.current_task_first_call:
|
||||
web_terminal.api_client.current_task_first_call = False
|
||||
update_thinking_after_call(web_terminal)
|
||||
|
||||
# 检测是否有格式错误的工具调用
|
||||
if not tool_calls and full_response and AUTO_FIX_TOOL_CALL:
|
||||
if detect_malformed_tool_call(full_response):
|
||||
@ -2044,7 +2013,6 @@ async def handle_task_with_sender(
|
||||
sender('system_message', {
|
||||
'content': f'⚠️ 自动修复: {fix_message}'
|
||||
})
|
||||
maybe_mark_failure_from_message(web_terminal, f'⚠️ 自动修复: {fix_message}')
|
||||
|
||||
messages.append({
|
||||
"role": "user",
|
||||
@ -2058,7 +2026,6 @@ async def handle_task_with_sender(
|
||||
sender('system_message', {
|
||||
'content': f'⌘ 工具调用格式错误,自动修复失败。请手动检查并重试。'
|
||||
})
|
||||
maybe_mark_failure_from_message(web_terminal, '⌘ 工具调用格式错误,自动修复失败。请手动检查并重试。')
|
||||
break
|
||||
|
||||
# 构建助手消息(用于API继续对话)
|
||||
@ -2166,14 +2133,12 @@ async def handle_task_with_sender(
|
||||
sender('system_message', {
|
||||
'content': f'⚠️ 检测到重复调用 {tool_name} 工具 {MAX_CONSECUTIVE_SAME_TOOL} 次,可能存在循环。'
|
||||
})
|
||||
maybe_mark_failure_from_message(web_terminal, f'⚠️ 检测到重复调用 {tool_name} 工具 {MAX_CONSECUTIVE_SAME_TOOL} 次,可能存在循环。')
|
||||
|
||||
if consecutive_same_tool[tool_name] >= MAX_CONSECUTIVE_SAME_TOOL + 2:
|
||||
debug_log(f"终止: 工具 {tool_name} 调用次数过多")
|
||||
sender('system_message', {
|
||||
'content': f'⌘ 工具 {tool_name} 重复调用过多,任务终止。'
|
||||
})
|
||||
maybe_mark_failure_from_message(web_terminal, f'⌘ 工具 {tool_name} 重复调用过多,任务终止。')
|
||||
break
|
||||
else:
|
||||
consecutive_same_tool.clear()
|
||||
@ -2208,8 +2173,6 @@ async def handle_task_with_sender(
|
||||
process_sub_agent_updates=process_sub_agent_updates,
|
||||
process_background_command_updates=process_background_command_updates,
|
||||
process_multi_agent_master_messages=process_multi_agent_master_messages,
|
||||
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
|
||||
mark_force_thinking=mark_force_thinking,
|
||||
get_stop_flag=get_stop_flag,
|
||||
clear_stop_flag=clear_stop_flag,
|
||||
workspace=workspace,
|
||||
|
||||
@ -150,7 +150,7 @@ def inject_runtime_user_message(
|
||||
return formatted
|
||||
|
||||
|
||||
async def process_sub_agent_updates(*, messages: List[Dict], inline: bool = False, after_tool_call_id: Optional[str] = None, web_terminal, sender, debug_log, maybe_mark_failure_from_message):
|
||||
async def process_sub_agent_updates(*, messages: List[Dict], inline: bool = False, after_tool_call_id: Optional[str] = None, web_terminal, sender, debug_log):
|
||||
"""轮询子智能体任务并通知前端,并把结果插入当前对话上下文。"""
|
||||
manager = getattr(web_terminal, "sub_agent_manager", None)
|
||||
if not manager:
|
||||
@ -263,10 +263,9 @@ async def process_sub_agent_updates(*, messages: List[Dict], inline: bool = Fals
|
||||
if inline:
|
||||
web_terminal._inline_sub_agent_notified.add(inline_key)
|
||||
debug_log(f"[SubAgent] 插入子智能体通知 after_tool_call_id={after_tool_call_id} inline={inline}")
|
||||
maybe_mark_failure_from_message(web_terminal, message)
|
||||
|
||||
|
||||
async def process_background_command_updates(*, messages: List[Dict], inline: bool = False, after_tool_call_id: Optional[str] = None, web_terminal, sender, debug_log, maybe_mark_failure_from_message):
|
||||
async def process_background_command_updates(*, messages: List[Dict], inline: bool = False, after_tool_call_id: Optional[str] = None, web_terminal, sender, debug_log):
|
||||
"""轮询后台 run_command 完成状态并发送 system 消息通知。"""
|
||||
manager = getattr(web_terminal, "background_command_manager", None)
|
||||
if not manager:
|
||||
@ -313,7 +312,6 @@ async def process_background_command_updates(*, messages: List[Dict], inline: bo
|
||||
except Exception:
|
||||
debug_log(f"[BgCmdDebug] mark_notified failed command_id={command_id}")
|
||||
pass
|
||||
maybe_mark_failure_from_message(web_terminal, message)
|
||||
|
||||
|
||||
|
||||
|
||||
@ -261,7 +261,7 @@ async def _wait_for_user_questions(*, question_ids: List[str], username: str, ti
|
||||
return answered
|
||||
|
||||
|
||||
async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, client_sid: str, username: str, iteration: int, conversation_id: Optional[str], last_tool_call_time: float, process_sub_agent_updates, process_background_command_updates, process_multi_agent_master_messages=process_multi_agent_master_messages, maybe_mark_failure_from_message, mark_force_thinking, get_stop_flag, clear_stop_flag, workspace=None):
|
||||
async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, client_sid: str, username: str, iteration: int, conversation_id: Optional[str], last_tool_call_time: float, process_sub_agent_updates, process_background_command_updates, process_multi_agent_master_messages=process_multi_agent_master_messages, get_stop_flag, clear_stop_flag, workspace=None):
|
||||
previous_tool_loop_active = getattr(web_terminal, "_tool_loop_active", False)
|
||||
web_terminal._tool_loop_active = True
|
||||
allowed_tool_names = set()
|
||||
@ -1240,7 +1240,6 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
||||
inline=False,
|
||||
extra_metadata={"task_id": result_data.get("task_id")},
|
||||
)
|
||||
maybe_mark_failure_from_message(web_terminal, system_message)
|
||||
|
||||
# 自动深层压缩(工具调用后触发)
|
||||
current_context_tokens = web_terminal.context_manager.get_current_context_tokens(conversation_id)
|
||||
@ -1263,9 +1262,6 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
||||
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
if tool_failed:
|
||||
mark_force_thinking(web_terminal, reason=f"{function_name}_failed")
|
||||
|
||||
# 自动深层压缩必须等待同一轮全部 tool_call 的 tool 消息都已写入 messages/历史后再触发。
|
||||
if deep_compression_pending and not web_terminal.context_manager.is_compression_in_progress():
|
||||
web_terminal.context_manager._set_meta_flag("is_ultra_long_conversation", True)
|
||||
@ -1308,7 +1304,6 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
||||
web_terminal=web_terminal,
|
||||
sender=sender,
|
||||
debug_log=debug_log,
|
||||
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
|
||||
)
|
||||
debug_log(
|
||||
"[BgCmdDebug] after all tools finished -> poll background command updates "
|
||||
@ -1321,7 +1316,6 @@ async def execute_tool_calls(*, web_terminal, tool_calls, sender, messages, clie
|
||||
web_terminal=web_terminal,
|
||||
sender=sender,
|
||||
debug_log=debug_log,
|
||||
maybe_mark_failure_from_message=maybe_mark_failure_from_message,
|
||||
)
|
||||
await process_multi_agent_master_messages(
|
||||
messages=messages,
|
||||
|
||||
@ -145,12 +145,32 @@ def _apply_workspace_personalization_preferences(terminal: WebTerminal, workspac
|
||||
and not bool(session_model)
|
||||
and not bool(getattr(terminal, "_workspace_default_model_applied", False))
|
||||
)
|
||||
terminal.apply_personalization_preferences(config, apply_default_model=apply_default_model)
|
||||
# 对话级 terminal 的 模型/思考模式/推理强度 以对话 meta 为权威,
|
||||
# 此函数在每次 /api/status、任务创建、加载资源时都会触发,
|
||||
# 不得在对话加载后反复用 prefs 默认值覆盖 meta 恢复值。
|
||||
# 工作区级 terminal(/new 页)同理:三项 modes 默认值仅首次应用一次,
|
||||
# 之后用户在 /new 手动调整的 模式/档位 必须稳定存活到创建对话时,
|
||||
# 不能被 status 轮询反复重置(prefs 更新走 settings 保存路径显式应用,
|
||||
# 新建空对话走 create_new_conversation 的 prefer_defaults 路径重置)。
|
||||
apply_default_modes = (
|
||||
not is_conversation_bound
|
||||
and not bool(getattr(terminal, "_workspace_default_modes_applied", False))
|
||||
)
|
||||
terminal.apply_personalization_preferences(
|
||||
config,
|
||||
apply_default_model=apply_default_model,
|
||||
apply_default_modes=apply_default_modes,
|
||||
)
|
||||
if apply_default_model:
|
||||
try:
|
||||
terminal._workspace_default_model_applied = True
|
||||
except Exception:
|
||||
pass
|
||||
if apply_default_modes:
|
||||
try:
|
||||
terminal._workspace_default_modes_applied = True
|
||||
except Exception:
|
||||
pass
|
||||
if has_request_context() and update_session:
|
||||
session["run_mode"] = getattr(terminal, "run_mode", session.get("run_mode"))
|
||||
session["thinking_mode"] = getattr(terminal, "thinking_mode", session.get("thinking_mode"))
|
||||
@ -631,9 +651,13 @@ def apply_conversation_overrides(terminal: WebTerminal, workspace, conversation_
|
||||
else:
|
||||
terminal.context_manager.custom_personalization_config = None
|
||||
|
||||
# 应用个性化偏好(含禁用工具分类)到当前终端
|
||||
# 应用个性化偏好(含禁用工具分类)到当前终端;
|
||||
# 对话加载链路不应用默认 模型/模式/推理强度(以对话 meta 为权威)
|
||||
try:
|
||||
terminal.apply_personalization_preferences(terminal.context_manager.custom_personalization_config)
|
||||
terminal.apply_personalization_preferences(
|
||||
terminal.context_manager.custom_personalization_config,
|
||||
apply_default_modes=False,
|
||||
)
|
||||
except Exception as exc:
|
||||
debug_log(f"[apply_overrides] 应用个性化失败: {exc}")
|
||||
except Exception as exc:
|
||||
@ -745,10 +769,10 @@ def ensure_conversation_loaded(
|
||||
terminal.set_run_mode("thinking")
|
||||
else:
|
||||
terminal.set_run_mode("fast")
|
||||
if terminal.thinking_mode:
|
||||
terminal.api_client.start_new_task(force_deep=terminal.deep_thinking_mode)
|
||||
else:
|
||||
terminal.api_client.start_new_task()
|
||||
try:
|
||||
terminal.set_reasoning_effort(meta.get("reasoning_effort"))
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
if has_request_context():
|
||||
session['run_mode'] = terminal.run_mode
|
||||
session['thinking_mode'] = terminal.thinking_mode
|
||||
@ -790,9 +814,6 @@ def reset_system_state(terminal: Optional[WebTerminal]):
|
||||
if not terminal:
|
||||
return
|
||||
try:
|
||||
if hasattr(terminal, 'api_client') and terminal.api_client:
|
||||
debug_log("重置API客户端状态")
|
||||
terminal.api_client.start_new_task(force_deep=getattr(terminal, "deep_thinking_mode", False))
|
||||
if hasattr(terminal, 'current_session_id'):
|
||||
terminal.current_session_id += 1
|
||||
debug_log(f"重置会话ID为: {terminal.current_session_id}")
|
||||
|
||||
@ -33,16 +33,14 @@ from config import (
|
||||
DEFAULT_PROJECT_PATH,
|
||||
LOGS_DIR,
|
||||
AGENT_VERSION,
|
||||
THINKING_FAST_INTERVAL,
|
||||
PROJECT_MAX_STORAGE_MB,
|
||||
PROJECT_MAX_STORAGE_BYTES,
|
||||
UPLOAD_SCAN_LOG_SUBDIR,
|
||||
REASONING_EFFORT_LEVELS,
|
||||
)
|
||||
from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
save_personalization_config,
|
||||
THINKING_INTERVAL_MIN,
|
||||
THINKING_INTERVAL_MAX,
|
||||
)
|
||||
from modules.upload_security import UploadSecurityError
|
||||
from modules.user_manager import UserWorkspace
|
||||
@ -189,6 +187,8 @@ def _build_safe_load_result(terminal: WebTerminal, conversation_id: str) -> Dict
|
||||
}
|
||||
meta = data.get("metadata", {}) or {}
|
||||
run_mode = meta.get("run_mode") or getattr(terminal, "run_mode", "fast")
|
||||
if run_mode == "deep": # 旧版标识符映射
|
||||
run_mode = "thinking"
|
||||
thinking_mode = bool(meta.get("thinking_mode", run_mode != "fast"))
|
||||
return {
|
||||
"success": True,
|
||||
@ -197,6 +197,7 @@ def _build_safe_load_result(terminal: WebTerminal, conversation_id: str) -> Dict
|
||||
"messages_count": len(data.get("messages", []) or []),
|
||||
"run_mode": run_mode,
|
||||
"thinking_mode": thinking_mode,
|
||||
"reasoning_effort": meta.get("reasoning_effort"),
|
||||
"model_key": meta.get("model_key") or getattr(terminal, "model_key", None),
|
||||
"message": f"对话已加载: {normalized_id}",
|
||||
"safe_navigation": True,
|
||||
@ -601,6 +602,22 @@ def create_conversation(terminal: WebTerminal, workspace: UserWorkspace, usernam
|
||||
run_mode = data.get('mode') if preserve_mode and 'mode' in data else None
|
||||
target_workspace_id = (data.get('workspace_id') or '').strip()
|
||||
multi_agent_mode = bool(data.get('multi_agent_mode'))
|
||||
# /new 页发消息/新建对话时前端随 body 传入当前生效的推理强度,
|
||||
# 权威写入新对话 meta;未提供时回落 terminal 当前档位 / 个性化默认值
|
||||
body_effort_provided = 'reasoning_effort' in data
|
||||
body_effort = None
|
||||
if body_effort_provided:
|
||||
raw_effort = data.get('reasoning_effort')
|
||||
if raw_effort is None:
|
||||
body_effort = None # 显式默认档(不传参)
|
||||
elif isinstance(raw_effort, str):
|
||||
candidate_effort = raw_effort.strip().lower()
|
||||
if candidate_effort in REASONING_EFFORT_LEVELS:
|
||||
body_effort = candidate_effort
|
||||
else:
|
||||
body_effort_provided = False # 非法值视为未提供
|
||||
else:
|
||||
body_effort_provided = False
|
||||
|
||||
if target_workspace_id:
|
||||
try:
|
||||
@ -626,15 +643,34 @@ def create_conversation(terminal: WebTerminal, workspace: UserWorkspace, usernam
|
||||
prefs = load_personalization_config(workspace.data_dir)
|
||||
except Exception:
|
||||
prefs = {}
|
||||
safe_run_mode = run_mode
|
||||
if safe_run_mode not in {"fast", "thinking", "deep"}:
|
||||
candidate = (prefs or {}).get("default_run_mode")
|
||||
safe_run_mode = candidate if candidate in {"fast", "thinking", "deep"} else "fast"
|
||||
safe_run_mode = str(run_mode or "").strip().lower()
|
||||
if safe_run_mode == "deep": # 旧版标识符映射
|
||||
safe_run_mode = "thinking"
|
||||
if safe_run_mode not in {"fast", "thinking"}:
|
||||
candidate = str((prefs or {}).get("default_run_mode") or "").strip().lower()
|
||||
if candidate == "deep":
|
||||
candidate = "thinking"
|
||||
safe_run_mode = candidate if candidate in {"fast", "thinking"} else "fast"
|
||||
safe_thinking = bool(thinking_mode) if thinking_mode is not None else safe_run_mode != "fast"
|
||||
previous_cm_current = getattr(cm, "current_conversation_id", None)
|
||||
default_permission_mode = (prefs or {}).get("default_permission_mode")
|
||||
if default_permission_mode not in ("readonly", "approval", "auto_approval", "unrestricted"):
|
||||
default_permission_mode = None
|
||||
# 推理强度优先级:body 显式值 > terminal 当前档位 > 个性化默认值
|
||||
if body_effort_provided:
|
||||
default_effort = body_effort
|
||||
else:
|
||||
terminal_effort = getattr(terminal, "reasoning_effort", None)
|
||||
if isinstance(terminal_effort, str) and terminal_effort.strip().lower() in REASONING_EFFORT_LEVELS:
|
||||
default_effort = terminal_effort.strip().lower()
|
||||
else:
|
||||
default_effort = (prefs or {}).get("default_reasoning_effort")
|
||||
if isinstance(default_effort, str):
|
||||
default_effort = default_effort.strip().lower() or None
|
||||
if default_effort not in REASONING_EFFORT_LEVELS:
|
||||
default_effort = None
|
||||
else:
|
||||
default_effort = None
|
||||
conversation_id = cm.create_conversation(
|
||||
project_path=str(workspace.project_path),
|
||||
thinking_mode=safe_thinking,
|
||||
@ -645,6 +681,7 @@ def create_conversation(terminal: WebTerminal, workspace: UserWorkspace, usernam
|
||||
"permission_mode": default_permission_mode or getattr(terminal, "get_permission_mode", lambda: "unrestricted")(),
|
||||
"execution_mode": getattr(terminal, "get_execution_mode", lambda: "sandbox")(),
|
||||
"multi_agent_mode": bool(multi_agent_mode),
|
||||
"reasoning_effort": default_effort,
|
||||
},
|
||||
)
|
||||
try:
|
||||
@ -665,10 +702,15 @@ def create_conversation(terminal: WebTerminal, workspace: UserWorkspace, usernam
|
||||
"safe_navigation": True,
|
||||
}
|
||||
else:
|
||||
# body 显式携带档位时权威覆盖(prefer_defaults 路径内部会应用 prefs 默认值,
|
||||
# 必须以用户实际选择为准)
|
||||
create_meta_overrides = {"multi_agent_mode": bool(multi_agent_mode)}
|
||||
if body_effort_provided:
|
||||
create_meta_overrides["reasoning_effort"] = body_effort
|
||||
result = terminal.create_new_conversation(
|
||||
thinking_mode=thinking_mode,
|
||||
run_mode=run_mode,
|
||||
metadata_overrides={"multi_agent_mode": bool(multi_agent_mode)},
|
||||
metadata_overrides=create_meta_overrides,
|
||||
)
|
||||
# 同步 terminal 级别的多智能体开关,避免新对话继承旧状态
|
||||
terminal.multi_agent_mode = bool(multi_agent_mode)
|
||||
@ -1366,6 +1408,7 @@ def _restore_checkpoint_to_conversation(
|
||||
restore_project_path = restore_meta.get("project_path") or str(workspace.project_path)
|
||||
restore_thinking_mode = bool(restore_meta.get("thinking_mode", False))
|
||||
restore_run_mode = restore_meta.get("run_mode") or ("thinking" if restore_thinking_mode else "fast")
|
||||
restore_reasoning_effort = restore_meta.get("reasoning_effort")
|
||||
restore_model_key = restore_meta.get("model_key")
|
||||
restore_has_images = bool(restore_meta.get("has_images", False))
|
||||
restore_has_videos = bool(restore_meta.get("has_videos", False))
|
||||
@ -1376,6 +1419,7 @@ def _restore_checkpoint_to_conversation(
|
||||
project_path=restore_project_path,
|
||||
thinking_mode=restore_thinking_mode,
|
||||
run_mode=restore_run_mode,
|
||||
reasoning_effort=restore_reasoning_effort,
|
||||
model_key=restore_model_key,
|
||||
has_images=restore_has_images,
|
||||
has_videos=restore_has_videos,
|
||||
@ -2276,8 +2320,6 @@ def _execute_system_command(terminal: WebTerminal, command: str) -> Dict[str, An
|
||||
|
||||
if cmd == "clear":
|
||||
terminal.context_manager.conversation_history.clear()
|
||||
if terminal.thinking_mode:
|
||||
terminal.api_client.start_new_task(force_deep=terminal.deep_thinking_mode)
|
||||
return {
|
||||
'command': cmd,
|
||||
'success': True,
|
||||
|
||||
@ -171,6 +171,8 @@ def bootstrap_conversation(conversation_id: str):
|
||||
raw_meta = conversation_data.get("metadata", {}) or {}
|
||||
messages = conversation_data.get("messages", []) or []
|
||||
run_mode = raw_meta.get("run_mode") or ("thinking" if raw_meta.get("thinking_mode") else "fast")
|
||||
if run_mode == "deep": # 旧版标识符映射
|
||||
run_mode = "thinking"
|
||||
|
||||
# 运行状态聚合(对齐 tasks/api.py running-status:主 task + 三类后台)
|
||||
main_rec = _find_active_main_task(username, normalized_id)
|
||||
@ -209,6 +211,7 @@ def bootstrap_conversation(conversation_id: str):
|
||||
"title": conversation_data.get("title", "未知对话"),
|
||||
"run_mode": run_mode,
|
||||
"thinking_mode": bool(raw_meta.get("thinking_mode", run_mode != "fast")),
|
||||
"reasoning_effort": raw_meta.get("reasoning_effort"),
|
||||
"model_key": raw_meta.get("model_key"),
|
||||
"multi_agent_mode": bool(raw_meta.get("multi_agent_mode", False)),
|
||||
"permission_mode": raw_meta.get("permission_mode") or "",
|
||||
|
||||
@ -26,6 +26,7 @@ from modules.multi_agent.role_store import (
|
||||
sync_preset_roles,
|
||||
)
|
||||
from config.paths import CUSTOM_ROLES_DIR, WEB_PRESET_ROLES_DIR
|
||||
from config.limits import REASONING_EFFORT_LEVELS
|
||||
|
||||
multi_agent_bp = Blueprint("multi_agent", __name__)
|
||||
|
||||
@ -285,6 +286,21 @@ def create_multi_agent_conversation():
|
||||
preserve_mode = bool(data.get("preserve_mode"))
|
||||
thinking_mode = data.get("thinking_mode") if preserve_mode and "thinking_mode" in data else None
|
||||
run_mode = data.get("mode") if preserve_mode and "mode" in data else None
|
||||
# 前端随 body 传入当前生效的推理强度,权威写入新对话 meta
|
||||
body_effort_provided = "reasoning_effort" in data
|
||||
body_effort = None
|
||||
if body_effort_provided:
|
||||
raw_effort = data.get("reasoning_effort")
|
||||
if raw_effort is None:
|
||||
body_effort = None
|
||||
elif isinstance(raw_effort, str):
|
||||
candidate_effort = raw_effort.strip().lower()
|
||||
if candidate_effort in REASONING_EFFORT_LEVELS:
|
||||
body_effort = candidate_effort
|
||||
else:
|
||||
body_effort_provided = False
|
||||
else:
|
||||
body_effort_provided = False
|
||||
|
||||
effective_workspace_id = target_workspace_id or session.get("workspace_id") or "default"
|
||||
active_task = _get_active_workspace_task(username=username, workspace_id=effective_workspace_id)
|
||||
@ -301,10 +317,14 @@ def create_multi_agent_conversation():
|
||||
if not cm:
|
||||
return jsonify({"success": False, "error": "对话管理器未初始化"}), 500
|
||||
|
||||
safe_run_mode = run_mode
|
||||
if safe_run_mode not in {"fast", "thinking", "deep"}:
|
||||
candidate = (prefs or {}).get("default_run_mode")
|
||||
safe_run_mode = candidate if candidate in {"fast", "thinking", "deep"} else "fast"
|
||||
safe_run_mode = str(run_mode or "").strip().lower()
|
||||
if safe_run_mode == "deep": # 旧版标识符映射
|
||||
safe_run_mode = "thinking"
|
||||
if safe_run_mode not in {"fast", "thinking"}:
|
||||
candidate = str((prefs or {}).get("default_run_mode") or "").strip().lower()
|
||||
if candidate == "deep":
|
||||
candidate = "thinking"
|
||||
safe_run_mode = candidate if candidate in {"fast", "thinking"} else "fast"
|
||||
safe_thinking = bool(thinking_mode) if thinking_mode is not None else safe_run_mode != "fast"
|
||||
default_permission_mode = (prefs or {}).get("default_permission_mode")
|
||||
if default_permission_mode not in ("readonly", "approval", "auto_approval", "unrestricted"):
|
||||
@ -312,6 +332,22 @@ def create_multi_agent_conversation():
|
||||
|
||||
previous_cm_current = getattr(ctx_manager, "current_conversation_id", None)
|
||||
|
||||
# 推理强度优先级:body 显式值 > terminal 当前档位 > 个性化默认值
|
||||
if body_effort_provided:
|
||||
ma_effort = body_effort
|
||||
else:
|
||||
terminal_effort = getattr(terminal, "reasoning_effort", None)
|
||||
if isinstance(terminal_effort, str) and terminal_effort.strip().lower() in REASONING_EFFORT_LEVELS:
|
||||
ma_effort = terminal_effort.strip().lower()
|
||||
else:
|
||||
ma_effort = (prefs or {}).get("default_reasoning_effort")
|
||||
if isinstance(ma_effort, str):
|
||||
ma_effort = ma_effort.strip().lower() or None
|
||||
if ma_effort not in REASONING_EFFORT_LEVELS:
|
||||
ma_effort = None
|
||||
else:
|
||||
ma_effort = None
|
||||
|
||||
conversation_id = cm.create_conversation(
|
||||
project_path=str(workspace.project_path),
|
||||
thinking_mode=safe_thinking,
|
||||
@ -322,6 +358,7 @@ def create_multi_agent_conversation():
|
||||
"permission_mode": default_permission_mode or getattr(terminal, "get_permission_mode", lambda: "unrestricted")(),
|
||||
"execution_mode": getattr(terminal, "get_execution_mode", lambda: "sandbox")(),
|
||||
"multi_agent_mode": True,
|
||||
"reasoning_effort": ma_effort,
|
||||
},
|
||||
)
|
||||
# 恢复 context_manager 的当前对话(不要影响普通对话的 current_conversation_id)
|
||||
|
||||
@ -61,7 +61,6 @@ PROJECT_STORAGE_CACHE_TTL_SECONDS = float(os.environ.get("PROJECT_STORAGE_CACHE_
|
||||
|
||||
# 其他配置
|
||||
DEFAULT_PORT = WEB_SERVER_PORT
|
||||
THINKING_FAILURE_KEYWORDS = ["⚠️", "🛑", "失败", "错误", "异常", "终止", "error", "failed", "未完成", "超时", "强制"]
|
||||
CSRF_HEADER_NAME = "X-CSRF-Token"
|
||||
CSRF_SESSION_KEY = "_csrf_token"
|
||||
CSRF_SAFE_METHODS = {"GET", "HEAD", "OPTIONS", "TRACE"}
|
||||
@ -113,7 +112,6 @@ __all__ = [
|
||||
"PROJECT_STORAGE_CACHE",
|
||||
"PROJECT_STORAGE_CACHE_TTL_SECONDS",
|
||||
"DEFAULT_PORT",
|
||||
"THINKING_FAILURE_KEYWORDS",
|
||||
"CSRF_HEADER_NAME",
|
||||
"CSRF_SESSION_KEY",
|
||||
"CSRF_SAFE_METHODS",
|
||||
|
||||
@ -788,7 +788,7 @@ class TaskManager:
|
||||
f"model_key={getattr(terminal, 'model_key', None)!r} "
|
||||
f"run_mode={getattr(terminal, 'run_mode', None)!r} "
|
||||
f"thinking_mode={getattr(terminal, 'thinking_mode', None)!r} "
|
||||
f"deep_mode={getattr(terminal, 'deep_thinking_mode', None)!r}"
|
||||
f"reasoning_effort={getattr(terminal, 'reasoning_effort', None)!r}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@ -809,7 +809,7 @@ class TaskManager:
|
||||
f"model_key={getattr(terminal, 'model_key', None)!r} "
|
||||
f"run_mode={getattr(terminal, 'run_mode', None)!r} "
|
||||
f"thinking_mode={getattr(terminal, 'thinking_mode', None)!r} "
|
||||
f"deep_mode={getattr(terminal, 'deep_thinking_mode', None)!r}"
|
||||
f"reasoning_effort={getattr(terminal, 'reasoning_effort', None)!r}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -225,6 +225,12 @@
|
||||
<div class="item-desc">{{ option.desc }}</div>
|
||||
<span v-if="option.value === resolvedRunMode" class="item-check">✓</span>
|
||||
</button>
|
||||
<div v-if="showEffortSlider" class="dropdown-effort">
|
||||
<EffortSlider
|
||||
:model-value="reasoningEffort"
|
||||
@update:model-value="setReasoningEffort"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
@ -751,6 +757,12 @@
|
||||
<div class="item-label">{{ option.label }}</div>
|
||||
<span v-if="option.value === resolvedRunMode" class="item-check">✓</span>
|
||||
</button>
|
||||
<div v-if="showEffortSlider" class="dropdown-effort">
|
||||
<EffortSlider
|
||||
:model-value="reasoningEffort"
|
||||
@update:model-value="setReasoningEffort"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
@ -8,6 +8,7 @@ import TerminalPanel from '../components/panels/TerminalPanel.vue';
|
||||
import TokenDrawer from '../components/token/TokenDrawer.vue';
|
||||
import QuickMenu from '../components/input/QuickMenu.vue';
|
||||
import InputComposer from '../components/input/InputComposer.vue';
|
||||
import EffortSlider from '../components/input/EffortSlider.vue';
|
||||
import AppShell from '../components/shell/AppShell.vue';
|
||||
import StatusAvatar from '../components/avatar/StatusAvatar.vue';
|
||||
|
||||
@ -57,6 +58,7 @@ export const appComponents = {
|
||||
PersonalizationDrawer,
|
||||
QuickMenu,
|
||||
InputComposer,
|
||||
EffortSlider,
|
||||
AppShell,
|
||||
StatusAvatar,
|
||||
ImagePicker,
|
||||
|
||||
@ -61,7 +61,8 @@ export const computed = {
|
||||
'projectPath',
|
||||
'agentVersion',
|
||||
'thinkingMode',
|
||||
'runMode'
|
||||
'runMode',
|
||||
'reasoningEffort'
|
||||
]),
|
||||
...mapState(useFileStore, ['contextMenu', 'fileTree', 'expandedFolders', 'todoList']),
|
||||
...mapWritableState(useUiStore, [
|
||||
@ -138,23 +139,29 @@ export const computed = {
|
||||
'goalDialogOpen'
|
||||
]),
|
||||
resolvedRunMode() {
|
||||
const allowed = ['fast', 'thinking', 'deep'];
|
||||
if (allowed.includes(this.runMode)) {
|
||||
return this.runMode;
|
||||
// 历史值 deep 映射为 thinking
|
||||
const raw = (this.runMode as string) === 'deep' ? 'thinking' : this.runMode;
|
||||
const allowed = ['fast', 'thinking'];
|
||||
if (allowed.includes(raw)) {
|
||||
return raw;
|
||||
}
|
||||
return this.thinkingMode ? 'thinking' : 'fast';
|
||||
},
|
||||
headerRunModeOptions() {
|
||||
return [
|
||||
{ value: 'fast', label: '快速模式', desc: '低思考,响应更快' },
|
||||
{ value: 'thinking', label: '思考模式', desc: '更长思考,综合回答' },
|
||||
{ value: 'deep', label: '深度思考', desc: '持续推理,适合复杂任务' }
|
||||
{ value: 'thinking', label: '思考模式', desc: '持续推理,适合复杂任务' }
|
||||
];
|
||||
},
|
||||
headerRunModeLabel() {
|
||||
const current = this.headerRunModeOptions.find((o) => o.value === this.resolvedRunMode);
|
||||
return current ? current.label : '快速模式';
|
||||
},
|
||||
// 推理强度滑块:仅思考模式 + 当前模型支持推理强度参数时显示
|
||||
showEffortSlider() {
|
||||
const modelStore = useModelStore();
|
||||
return this.resolvedRunMode === 'thinking' && !!modelStore.currentModel?.supportsReasoningEffort;
|
||||
},
|
||||
currentModelLabel() {
|
||||
const modelStore = useModelStore();
|
||||
return modelStore.currentModel?.label || '未选择模型';
|
||||
|
||||
@ -54,6 +54,9 @@ export const actionMethods = {
|
||||
});
|
||||
this.logMessageState('createNewConversation:start');
|
||||
|
||||
// 新建对话前把 debounce 中的推理强度保存立即落盘(存到原对话)
|
||||
this.flushReasoningEffortSave?.();
|
||||
|
||||
await this.persistComposerDraftNow({
|
||||
reason: 'create-new-conversation',
|
||||
force: true,
|
||||
@ -67,12 +70,15 @@ export const actionMethods = {
|
||||
if (personalizationStore.loaded) {
|
||||
const defaultRunMode = personalizationStore.form.default_run_mode;
|
||||
const defaultModel = personalizationStore.form.default_model;
|
||||
const defaultReasoningEffort = personalizationStore.form.default_reasoning_effort;
|
||||
|
||||
if (defaultRunMode) {
|
||||
this.runMode = defaultRunMode;
|
||||
debugLog('应用默认运行模式:', defaultRunMode);
|
||||
}
|
||||
|
||||
this.reasoningEffort = defaultReasoningEffort;
|
||||
|
||||
if (defaultModel) {
|
||||
this.currentModelKey = defaultModel;
|
||||
debugLog('应用默认模型:', defaultModel);
|
||||
@ -124,9 +130,11 @@ export const actionMethods = {
|
||||
|
||||
const isMultiAgent = Boolean(this.multiAgentMode);
|
||||
const createUrl = isMultiAgent ? '/api/multiagent/conversations' : '/api/conversations';
|
||||
// reasoning_effort 随创建权威写入新对话 meta(此处 this.reasoningEffort 已被上方
|
||||
// 重置为个性化默认值,语义与“创建空对话写入默认值一次”一致)
|
||||
const createBody = isMultiAgent
|
||||
? JSON.stringify({ preserve_mode: true, thinking_mode: this.thinkingMode, mode: this.runMode })
|
||||
: JSON.stringify({ thinking_mode: this.thinkingMode, mode: this.runMode });
|
||||
? JSON.stringify({ preserve_mode: true, thinking_mode: this.thinkingMode, mode: this.runMode, reasoning_effort: this.reasoningEffort })
|
||||
: JSON.stringify({ thinking_mode: this.thinkingMode, mode: this.runMode, reasoning_effort: this.reasoningEffort });
|
||||
const response = await fetch(createUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@ -49,7 +49,8 @@ export const bootstrapMethods = {
|
||||
|
||||
// 1. 应用模式/模型(与旧 PUT load 响应处理对齐)
|
||||
if (typeof meta.run_mode === 'string') {
|
||||
this.runMode = meta.run_mode;
|
||||
// 历史值 deep 映射为 thinking
|
||||
this.runMode = (meta.run_mode === 'deep' ? 'thinking' : meta.run_mode) as 'fast' | 'thinking';
|
||||
this.thinkingMode =
|
||||
typeof meta.thinking_mode === 'boolean' ? meta.thinking_mode : meta.run_mode !== 'fast';
|
||||
} else if (typeof meta.thinking_mode === 'boolean') {
|
||||
@ -59,6 +60,8 @@ export const bootstrapMethods = {
|
||||
if (typeof meta.model_key === 'string' && meta.model_key) {
|
||||
this.modelSet(meta.model_key);
|
||||
}
|
||||
// 恢复会话级推理强度档位(null = 默认,不传参)
|
||||
this.reasoningEffort = typeof meta.reasoning_effort === 'string' ? meta.reasoning_effort : null;
|
||||
if (typeof meta.multi_agent_mode === 'boolean') {
|
||||
this.multiAgentMode = meta.multi_agent_mode;
|
||||
}
|
||||
|
||||
@ -99,6 +99,10 @@ export const loadMethods = {
|
||||
return;
|
||||
}
|
||||
|
||||
// 切换对话前把 debounce 中的推理强度保存立即落盘(随请求带原对话 id,
|
||||
// 不会串到新对话);fire-and-forget,不阻塞加载
|
||||
this.flushReasoningEffortSave?.();
|
||||
|
||||
if ((this.compressionInProgress || this.compressing) && !force) {
|
||||
const confirmed = await this.confirmAction({
|
||||
title: '压缩进行中',
|
||||
|
||||
@ -243,9 +243,11 @@ export const sendMethods = {
|
||||
|
||||
const isMultiAgent = Boolean(this.multiAgentMode);
|
||||
const createUrl = isMultiAgent ? '/api/multiagent/conversations' : '/api/conversations';
|
||||
// reasoning_effort 随创建权威写入新对话 meta:/new 页面用户可能已手动调整档位,
|
||||
// 不能依赖后端 terminal 值(会被 status 轮询的 prefs 应用覆盖)
|
||||
const createBody = isMultiAgent
|
||||
? JSON.stringify({ preserve_mode: true, thinking_mode: this.thinkingMode, mode: this.runMode })
|
||||
: JSON.stringify({ thinking_mode: this.thinkingMode, mode: this.runMode });
|
||||
? JSON.stringify({ preserve_mode: true, thinking_mode: this.thinkingMode, mode: this.runMode, reasoning_effort: this.reasoningEffort })
|
||||
: JSON.stringify({ thinking_mode: this.thinkingMode, mode: this.runMode, reasoning_effort: this.reasoningEffort });
|
||||
const createResp = await fetch(createUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@ -130,16 +130,30 @@ export const resourceMethods = {
|
||||
const prevExecutionMode = this.currentExecutionMode;
|
||||
const prevDirectUntil = this.executionModeDirectUntil;
|
||||
this.resourceApplyStatusSnapshot(status);
|
||||
if (status && typeof status.thinking_mode !== 'undefined') {
|
||||
this.thinkingMode = !!status.thinking_mode;
|
||||
}
|
||||
if (status && typeof status.run_mode === 'string') {
|
||||
this.runMode = status.run_mode;
|
||||
} else if (status && typeof status.thinking_mode !== 'undefined') {
|
||||
this.runMode = status.thinking_mode ? 'thinking' : 'fast';
|
||||
}
|
||||
if (status && typeof status.model_key === 'string') {
|
||||
this.modelSet(status.model_key);
|
||||
// 模型/思考模式/推理强度的权威来源规则:
|
||||
// - 非空对话:以对话 meta 为准(enterConversation bootstrap 恢复),
|
||||
// status 快照不得覆盖(terminal 状态可能滞后于对话加载)
|
||||
// - 空对话/首页:terminal 状态即权威,从 status 同步
|
||||
const convId = this.currentConversationId;
|
||||
const hasConversation =
|
||||
typeof convId === 'string' && convId.length > 0 && !convId.startsWith('temp_');
|
||||
if (!hasConversation) {
|
||||
if (status && typeof status.thinking_mode !== 'undefined') {
|
||||
this.thinkingMode = !!status.thinking_mode;
|
||||
}
|
||||
if (status && typeof status.run_mode === 'string') {
|
||||
// 历史值 deep 映射为 thinking
|
||||
this.runMode = status.run_mode === 'deep' ? 'thinking' : status.run_mode;
|
||||
} else if (status && typeof status.thinking_mode !== 'undefined') {
|
||||
this.runMode = status.thinking_mode ? 'thinking' : 'fast';
|
||||
}
|
||||
if (status && Object.prototype.hasOwnProperty.call(status, 'reasoning_effort')) {
|
||||
this.reasoningEffort =
|
||||
typeof status.reasoning_effort === 'string' ? status.reasoning_effort : null;
|
||||
}
|
||||
if (status && typeof status.model_key === 'string') {
|
||||
this.modelSet(status.model_key);
|
||||
}
|
||||
}
|
||||
if (status && typeof status.permission_mode === 'string') {
|
||||
this.currentPermissionMode = status.permission_mode;
|
||||
|
||||
@ -23,6 +23,16 @@ import {
|
||||
parseSystemNoticeLabel,
|
||||
} from './shared';
|
||||
|
||||
// 推理强度滑块是高频交互(后端限制 20 次/分):档位变化只乐观更新 UI,
|
||||
// 停止操作 600ms 后才把最终值发一次请求;序列开始时的对话 id 随请求带上,
|
||||
// 即使 debounce 期间切换对话也能存到正确对话
|
||||
const effortSave = {
|
||||
timer: null as ReturnType<typeof setTimeout> | null,
|
||||
rollback: undefined as string | null | undefined, // undefined = 无 pending
|
||||
latest: undefined as string | null | undefined,
|
||||
conversationId: null as string | null
|
||||
};
|
||||
|
||||
export const modeMethods = {
|
||||
handleQuickModeToggle() {
|
||||
if (!this.isConnected || this.streamingMessage) {
|
||||
@ -106,7 +116,7 @@ export const modeMethods = {
|
||||
this.closeHeaderMenu();
|
||||
},
|
||||
async handleCycleRunMode() {
|
||||
const modes: Array<'fast' | 'thinking' | 'deep'> = ['fast', 'thinking', 'deep'];
|
||||
const modes: Array<'fast' | 'thinking'> = ['fast', 'thinking'];
|
||||
const currentMode = this.resolvedRunMode;
|
||||
const currentIndex = modes.indexOf(currentMode);
|
||||
const nextMode = modes[(currentIndex + 1) % modes.length];
|
||||
@ -117,9 +127,13 @@ export const modeMethods = {
|
||||
this.modeMenuOpen = false;
|
||||
return;
|
||||
}
|
||||
// 历史值 deep 映射为 thinking
|
||||
if (mode === 'deep') {
|
||||
mode = 'thinking';
|
||||
}
|
||||
const modelStore = useModelStore();
|
||||
const fastOnly = modelStore.currentModel?.fastOnly;
|
||||
const deepOnly = modelStore.currentModel?.deepOnly;
|
||||
const thinkingOnly = modelStore.currentModel?.thinkingOnly;
|
||||
if (fastOnly && mode !== 'fast') {
|
||||
if (!options.suppressToast) {
|
||||
this.uiPushToast({
|
||||
@ -132,11 +146,11 @@ export const modeMethods = {
|
||||
this.inputCloseMenus();
|
||||
return;
|
||||
}
|
||||
if (deepOnly && mode !== 'deep') {
|
||||
if (thinkingOnly && mode !== 'thinking') {
|
||||
if (!options.suppressToast) {
|
||||
this.uiPushToast({
|
||||
title: '模式不可用',
|
||||
message: '当前模型仅支持深度思考模式',
|
||||
message: '当前模型仅支持思考模式',
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
@ -181,6 +195,70 @@ export const modeMethods = {
|
||||
async toggleThinkingMode() {
|
||||
await this.handleCycleRunMode();
|
||||
},
|
||||
async setReasoningEffort(effort) {
|
||||
if (effortSave.rollback === undefined) {
|
||||
// 序列开始:记录回滚目标与所属对话
|
||||
effortSave.rollback = this.reasoningEffort;
|
||||
effortSave.conversationId = this.currentConversationId || null;
|
||||
}
|
||||
effortSave.latest = effort;
|
||||
// 乐观更新(UI 与后续创建对话 body 都读这个值,立即生效)
|
||||
this.reasoningEffort = effort;
|
||||
if (effortSave.timer) clearTimeout(effortSave.timer);
|
||||
effortSave.timer = setTimeout(() => {
|
||||
effortSave.timer = null;
|
||||
this.flushReasoningEffortSave();
|
||||
}, 600);
|
||||
},
|
||||
// 立即执行 pending 的推理强度保存(debounce 兜底:切换/新建对话前调用)
|
||||
async flushReasoningEffortSave() {
|
||||
if (effortSave.timer) {
|
||||
clearTimeout(effortSave.timer);
|
||||
effortSave.timer = null;
|
||||
}
|
||||
if (effortSave.rollback === undefined) return;
|
||||
const effort = effortSave.latest;
|
||||
const rollback = effortSave.rollback;
|
||||
const targetConversationId = effortSave.conversationId;
|
||||
effortSave.rollback = undefined;
|
||||
effortSave.latest = undefined;
|
||||
effortSave.conversationId = null;
|
||||
try {
|
||||
const response = await fetch('/api/reasoning-effort', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ effort, conversation_id: targetConversationId })
|
||||
});
|
||||
const payload = await response.json();
|
||||
if (!response.ok || !payload.success) {
|
||||
throw new Error(payload.message || payload.error || '设置失败');
|
||||
}
|
||||
const data = payload.data || {};
|
||||
// 仅当 UI 仍停留在该序列的值时才用服务端回写确认
|
||||
//(切对话后 UI 已恢复为新对话的值,不得覆盖)
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(data, 'reasoning_effort') &&
|
||||
this.reasoningEffort === effort
|
||||
) {
|
||||
this.reasoningEffort =
|
||||
typeof data.reasoning_effort === 'string' ? data.reasoning_effort : null;
|
||||
}
|
||||
} catch (error) {
|
||||
// 仅当 UI 仍停留在该序列的值时才回滚
|
||||
if (this.reasoningEffort === effort) {
|
||||
this.reasoningEffort = rollback;
|
||||
}
|
||||
console.error('设置推理强度失败:', error);
|
||||
const message = error instanceof Error ? error.message : String(error || '未知错误');
|
||||
this.uiPushToast({
|
||||
title: '设置推理强度失败',
|
||||
message: message || '请稍后重试',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
},
|
||||
async handleStopAllSubAgents() {
|
||||
const isMultiAgent = !!this.multiAgentMode;
|
||||
const mode = isMultiAgent ? 'soft_stop' : 'terminate';
|
||||
|
||||
@ -95,8 +95,8 @@ export const modelMethods = {
|
||||
} else {
|
||||
// 前端兼容策略:根据模型特性自动调整运行模式
|
||||
const currentModel = modelStore.currentModel;
|
||||
if (currentModel?.deepOnly) {
|
||||
this.runMode = 'deep';
|
||||
if (currentModel?.thinkingOnly) {
|
||||
this.runMode = 'thinking';
|
||||
this.thinkingMode = true;
|
||||
} else if (currentModel?.fastOnly) {
|
||||
this.runMode = 'fast';
|
||||
|
||||
@ -237,7 +237,6 @@ export const socketMethods = {
|
||||
this.socket = null;
|
||||
this.projectPath = statusData.project_path || '';
|
||||
this.agentVersion = statusData.version || this.agentVersion;
|
||||
this.thinkingMode = !!statusData.thinking_mode;
|
||||
this.applyStatusSnapshot(statusData);
|
||||
this.fetchPermissionMode();
|
||||
this.fetchExecutionMode();
|
||||
@ -276,12 +275,18 @@ export const socketMethods = {
|
||||
if (personalizationStore.loaded && !hasActiveConversation && !this.currentConversationId) {
|
||||
const defaultRunMode = personalizationStore.form.default_run_mode;
|
||||
const defaultModel = personalizationStore.form.default_model;
|
||||
const defaultReasoningEffort = personalizationStore.form.default_reasoning_effort;
|
||||
|
||||
if (defaultRunMode) {
|
||||
this.runMode = defaultRunMode;
|
||||
debugLog('应用默认运行模式:', defaultRunMode);
|
||||
}
|
||||
|
||||
this.reasoningEffort = defaultReasoningEffort;
|
||||
if (defaultReasoningEffort) {
|
||||
debugLog('应用默认推理强度:', defaultReasoningEffort);
|
||||
}
|
||||
|
||||
if (defaultModel) {
|
||||
modelStore.setModel(defaultModel);
|
||||
this.currentModelKey = modelStore.currentModelKey;
|
||||
|
||||
518
static/src/components/input/EffortSlider.vue
Normal file
518
static/src/components/input/EffortSlider.vue
Normal file
@ -0,0 +1,518 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import type { ReasoningEffort } from '../../stores/personalization';
|
||||
|
||||
const LEVELS: ReasoningEffort[] = ['low', 'medium', 'high', 'xhigh', 'max'];
|
||||
// 轨道两端档位圆钮中心 inset;比圆钮半径(23)小 3px,
|
||||
// 让圆钮在两端时外凸 3px,完整盖住填充条端头(避免相切处露出杂边)
|
||||
const EDGE = 20;
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: ReasoningEffort | null;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: ReasoningEffort | null): void;
|
||||
}>();
|
||||
|
||||
const trackRef = ref<HTMLElement | null>(null);
|
||||
const trackWidth = ref(0);
|
||||
// 当前档位(记忆值,勾选默认时也保留)
|
||||
const levelIndex = ref(2);
|
||||
// 圆钮/填充的目标像素位置(拖动时连续,否则吸附档位)
|
||||
const knobX = ref(0);
|
||||
// 圆钮/填充的视觉像素位置(JS rAF 插值驱动):点击/吸附时从当前
|
||||
// 视觉位置平滑滑向目标,天然连续无瞬移;圆点 covered 也以此为准 ——
|
||||
// 变色只在圆钮实际滑到/离开时发生,往低调档时被变色的点会被圆钮
|
||||
// 遮住渐变过程,不会暴露「白→黑」的闪烁
|
||||
const knobXVisual = ref(0);
|
||||
const activeLevel = ref(2);
|
||||
const dragging = ref(false);
|
||||
const liveDrag = ref(false);
|
||||
let downX = 0;
|
||||
let animFrame = 0;
|
||||
|
||||
const isDefault = computed(() => props.modelValue === null);
|
||||
|
||||
const posRatio = (i: number) => i / (LEVELS.length - 1);
|
||||
const posPct = (i: number) => `calc(${EDGE}px + (100% - ${EDGE * 2}px) * ${posRatio(i)})`;
|
||||
const posPx = (i: number) => EDGE + (trackWidth.value - EDGE * 2) * posRatio(i);
|
||||
const pxToLevel = (x: number) => {
|
||||
const w = trackWidth.value;
|
||||
if (w <= EDGE * 2) return 0;
|
||||
const ratio = (x - EDGE) / (w - EDGE * 2);
|
||||
return Math.max(0, Math.min(LEVELS.length - 1, Math.round(ratio * (LEVELS.length - 1))));
|
||||
};
|
||||
|
||||
const clampEventX = (clientX: number) => {
|
||||
const el = trackRef.value;
|
||||
if (!el) return EDGE;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const x = clientX - rect.left;
|
||||
return Math.max(EDGE, Math.min(rect.width - EDGE, x));
|
||||
};
|
||||
|
||||
const KNOB_ANIM_MS = 200;
|
||||
// 与 CSS ease 接近的 easeInOutQuad
|
||||
const easeInOut = (t: number) => (t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2);
|
||||
|
||||
const cancelKnobAnim = () => {
|
||||
if (animFrame) {
|
||||
cancelAnimationFrame(animFrame);
|
||||
animFrame = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const setKnobInstant = (x: number) => {
|
||||
cancelKnobAnim();
|
||||
knobXVisual.value = x;
|
||||
};
|
||||
|
||||
// 从当前视觉位置插值滑动到目标(进行中的动画被接续,不会跳变)
|
||||
const animateKnobTo = (target: number) => {
|
||||
const start = knobXVisual.value;
|
||||
if (start === target) return;
|
||||
cancelKnobAnim();
|
||||
const t0 = performance.now();
|
||||
const step = (now: number) => {
|
||||
const p = Math.min(1, (now - t0) / KNOB_ANIM_MS);
|
||||
knobXVisual.value = start + (target - start) * easeInOut(p);
|
||||
if (p < 1) {
|
||||
animFrame = requestAnimationFrame(step);
|
||||
} else {
|
||||
animFrame = 0;
|
||||
}
|
||||
};
|
||||
animFrame = requestAnimationFrame(step);
|
||||
};
|
||||
|
||||
// animate=true:点击/吸附时平滑滑动;false:挂载/resize/外部变更时瞬间就位
|
||||
const syncToLevel = (animate = false) => {
|
||||
const target = posPx(levelIndex.value);
|
||||
knobX.value = target;
|
||||
activeLevel.value = levelIndex.value;
|
||||
if (animate && trackWidth.value > EDGE * 2) {
|
||||
animateKnobTo(target);
|
||||
} else {
|
||||
setKnobInstant(target);
|
||||
}
|
||||
};
|
||||
|
||||
// 未测量(trackWidth=0 的首帧)时无法用像素定位:
|
||||
// 圆钮/填充退回百分比定位(与测量后像素值完全等价,切换无跳变),
|
||||
// 圆点 covered 退回档位语义 —— 打开弹窗首帧即最终状态,不会闪
|
||||
const knobLeftStyle = computed(() =>
|
||||
trackWidth.value > EDGE * 2 ? `${knobXVisual.value}px` : posPct(levelIndex.value)
|
||||
);
|
||||
const fillWidthStyle = computed(() =>
|
||||
trackWidth.value > EDGE * 2 ? `${knobXVisual.value}px` : posPct(levelIndex.value)
|
||||
);
|
||||
const dotCovered = (i: number) =>
|
||||
trackWidth.value > EDGE * 2 ? posPx(i) <= knobXVisual.value : i <= levelIndex.value;
|
||||
|
||||
const commitLevel = () => {
|
||||
emit('update:modelValue', LEVELS[levelIndex.value]);
|
||||
};
|
||||
|
||||
const onPointerDown = (e: PointerEvent) => {
|
||||
dragging.value = true;
|
||||
liveDrag.value = false;
|
||||
downX = e.clientX;
|
||||
trackRef.value?.setPointerCapture(e.pointerId);
|
||||
// 点击(含置灰状态):解除默认并平滑滑到目标档(JS 插值驱动)
|
||||
levelIndex.value = pxToLevel(clampEventX(e.clientX));
|
||||
commitLevel();
|
||||
syncToLevel(true);
|
||||
};
|
||||
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
if (!dragging.value) return;
|
||||
if (!liveDrag.value && Math.abs(e.clientX - downX) > 3) liveDrag.value = true;
|
||||
if (liveDrag.value) {
|
||||
// 真实拖动:无动画连续跟随
|
||||
const x = clampEventX(e.clientX);
|
||||
cancelKnobAnim();
|
||||
knobX.value = x;
|
||||
knobXVisual.value = x;
|
||||
activeLevel.value = pxToLevel(x);
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = (e: PointerEvent) => {
|
||||
if (!dragging.value) return;
|
||||
dragging.value = false;
|
||||
if (liveDrag.value) {
|
||||
// 拖动结束:吸附最近档。插值从松手点连续位置开始滑向档位,
|
||||
// 天然无瞬移(无需旧实现的分帧抑制)
|
||||
liveDrag.value = false;
|
||||
levelIndex.value = pxToLevel(clampEventX(e.clientX));
|
||||
commitLevel();
|
||||
syncToLevel(true);
|
||||
return;
|
||||
}
|
||||
syncToLevel(true);
|
||||
};
|
||||
|
||||
const toggleDefault = () => {
|
||||
emit('update:modelValue', isDefault.value ? LEVELS[levelIndex.value] : null);
|
||||
};
|
||||
|
||||
const measure = () => {
|
||||
trackWidth.value = trackRef.value?.clientWidth ?? 0;
|
||||
syncToLevel();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
if (value === null) return; // 勾选默认:保留档位记忆,圆钮不动
|
||||
const i = LEVELS.indexOf(value);
|
||||
if (i < 0 || i === levelIndex.value) return; // 自己 emit 的回声:不打扰进行中的动画
|
||||
// 外部变更(如父组件异步修正):瞬间同步
|
||||
levelIndex.value = i;
|
||||
syncToLevel(false);
|
||||
},
|
||||
// immediate:挂载时立即从 modelValue 同步档位,
|
||||
// 否则弹窗每次打开都停在初始值 high
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
measure();
|
||||
window.addEventListener('resize', measure);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
cancelKnobAnim();
|
||||
window.removeEventListener('resize', measure);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="effort-slider"
|
||||
:class="{ 'is-default': isDefault, 'live-drag': liveDrag }"
|
||||
>
|
||||
<!-- 顶部:非拖动 = 推理强度 + 默认;拖动中 = 更高效 / 更智能 -->
|
||||
<div class="effort-header">
|
||||
<div class="header-state header-idle">
|
||||
<span class="header-title">推理强度</span>
|
||||
<span class="default-toggle" :class="{ checked: isDefault }" @click.stop="toggleDefault">
|
||||
<span class="default-label">默认</span>
|
||||
<span class="fancy-check" aria-hidden="true">
|
||||
<svg viewBox="0 0 64 64">
|
||||
<path
|
||||
d="M 0 16 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 16 L 32 48 L 64 16 V 8 A 8 8 90 0 0 56 0 H 8 A 8 8 90 0 0 0 8 V 56 A 8 8 90 0 0 8 64 H 56 A 8 8 90 0 0 64 56 V 16"
|
||||
pathLength="575.0541381835938"
|
||||
class="fancy-path"
|
||||
></path>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="header-state header-drag">
|
||||
<span>更高效</span>
|
||||
<span>更智能</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 滑块 -->
|
||||
<div class="slider-zone">
|
||||
<div
|
||||
ref="trackRef"
|
||||
class="track"
|
||||
@pointerdown="onPointerDown"
|
||||
@pointermove="onPointerMove"
|
||||
@pointerup="onPointerUp"
|
||||
@pointercancel="onPointerUp"
|
||||
>
|
||||
<div class="fill-stack" :style="{ width: fillWidthStyle }">
|
||||
<div
|
||||
v-for="(_, i) in LEVELS"
|
||||
:key="`lv-${i}`"
|
||||
class="fill-layer"
|
||||
:class="[`lv-${i}`, { on: i === activeLevel }]"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
v-for="(_, i) in LEVELS"
|
||||
:key="`dot-${i}`"
|
||||
class="dot"
|
||||
:class="{ covered: dotCovered(i) }"
|
||||
:style="{ left: posPct(i) }"
|
||||
></div>
|
||||
<div class="knob" :style="{ left: knobLeftStyle }"></div>
|
||||
</div>
|
||||
<div class="level-labels">
|
||||
<span
|
||||
v-for="(name, i) in LEVELS"
|
||||
:key="`label-${i}`"
|
||||
class="level-label"
|
||||
:class="{ active: i === activeLevel }"
|
||||
:style="{ left: posPct(i) }"
|
||||
>{{ name }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.effort-slider {
|
||||
width: 100%;
|
||||
padding: 4px 2px 0;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
/* ========== 顶部:两种状态文字叠在一起,淡入淡出切换 ========== */
|
||||
.effort-header {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.header-state {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
transition:
|
||||
opacity 0.18s ease,
|
||||
transform 0.18s ease;
|
||||
}
|
||||
.header-idle {
|
||||
opacity: 1;
|
||||
}
|
||||
.header-drag {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
pointer-events: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
.effort-slider.live-drag .header-idle {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
pointer-events: none;
|
||||
}
|
||||
.effort-slider.live-drag .header-drag {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* ========== 「默认」打勾框:个人空间 fancy-check 缩小版 ========== */
|
||||
.default-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.default-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.default-toggle .fancy-check {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.default-toggle .fancy-check svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
overflow: visible;
|
||||
}
|
||||
.fancy-path {
|
||||
fill: none;
|
||||
stroke: var(--text-secondary);
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
transition:
|
||||
stroke-dasharray 0.5s ease,
|
||||
stroke-dashoffset 0.5s ease,
|
||||
stroke 0.2s ease;
|
||||
stroke-dasharray: 241 9999999;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
.default-toggle.checked .fancy-path {
|
||||
stroke-dasharray: 70.5096664428711 9999999;
|
||||
stroke-dashoffset: -262.2723388671875;
|
||||
}
|
||||
|
||||
/* ========== 滑块区域 ========== */
|
||||
.slider-zone {
|
||||
position: relative;
|
||||
transition:
|
||||
filter 0.25s ease,
|
||||
opacity 0.25s ease;
|
||||
}
|
||||
|
||||
/* 默认勾选时:整体置灰,且停掉所有动画 */
|
||||
.effort-slider.is-default .slider-zone {
|
||||
filter: grayscale(1) brightness(0.72);
|
||||
opacity: 0.55;
|
||||
}
|
||||
.effort-slider.is-default .fill-layer,
|
||||
.effort-slider.is-default .fill-layer::after {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.track {
|
||||
position: relative;
|
||||
height: 34px;
|
||||
border-radius: 17px;
|
||||
background: var(--effort-track-bg);
|
||||
cursor: pointer;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
/* 填充:5 层叠放,当前档淡入、其余淡出 —— 任何颜色切换都是平滑 crossfade */
|
||||
.fill-stack {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 23px;
|
||||
border-radius: 17px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.fill-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.35s ease;
|
||||
}
|
||||
.fill-layer.on {
|
||||
opacity: 1;
|
||||
}
|
||||
.fill-layer.lv-0 {
|
||||
background: var(--effort-low);
|
||||
}
|
||||
.fill-layer.lv-1 {
|
||||
background: var(--effort-medium);
|
||||
}
|
||||
.fill-layer.lv-2 {
|
||||
background: var(--effort-high);
|
||||
}
|
||||
.fill-layer.lv-3 {
|
||||
background: var(--effort-xhigh);
|
||||
}
|
||||
|
||||
/* xhigh:白光扫过(90deg 整列渐变无硬边;位移动画两端完全出画后回绕 → 无缝) */
|
||||
.fill-layer.lv-3::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -50%;
|
||||
width: 45%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
var(--effort-xhigh-shine) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
animation: shine-sweep 1.8s linear infinite;
|
||||
}
|
||||
@keyframes shine-sweep {
|
||||
from {
|
||||
left: -50%;
|
||||
}
|
||||
to {
|
||||
left: 130%;
|
||||
}
|
||||
}
|
||||
|
||||
/* max:彩虹流动(200% 宽 + 位移整数个循环单元 → 真无缝,方向从左到右) */
|
||||
.fill-layer.lv-4 {
|
||||
width: 200%;
|
||||
background: repeating-linear-gradient(
|
||||
90deg,
|
||||
var(--effort-max-red) 0%,
|
||||
var(--effort-max-orange) 7.15%,
|
||||
var(--effort-max-yellow) 14.3%,
|
||||
var(--effort-max-green) 21.45%,
|
||||
var(--effort-max-cyan) 28.6%,
|
||||
var(--effort-max-purple) 35.75%,
|
||||
var(--effort-max-pink) 42.9%,
|
||||
var(--effort-max-red) 50%
|
||||
);
|
||||
animation: rainbow-flow 2.4s linear infinite;
|
||||
}
|
||||
@keyframes rainbow-flow {
|
||||
from {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 圆钮与填充位置由 JS rAF 插值驱动(见 script):点击/吸附平滑滑动、
|
||||
拖动连续跟随、圆点变色时机与圆钮视觉位置严格同步 —— 不用 CSS transition */
|
||||
|
||||
/* 档位圆点 */
|
||||
.dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--effort-dot-uncovered);
|
||||
transition: background 0.2s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.dot.covered {
|
||||
background: var(--effort-dot-covered);
|
||||
}
|
||||
|
||||
/* 拖柄 */
|
||||
.knob {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 50%;
|
||||
background: var(--effort-knob-bg);
|
||||
border: 1px solid var(--border-default);
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 0 1px 4px var(--effort-knob-shadow);
|
||||
cursor: grab;
|
||||
z-index: 2;
|
||||
}
|
||||
.effort-slider.live-drag .knob {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* ========== 档位文字 ========== */
|
||||
.level-labels {
|
||||
position: relative;
|
||||
height: 18px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.level-label {
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
transition:
|
||||
color 0.2s ease,
|
||||
font-weight 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.level-label.active {
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@ -570,7 +570,7 @@ const props = defineProps<{
|
||||
inputLocked: boolean;
|
||||
uploading: boolean;
|
||||
thinkingMode: boolean;
|
||||
runMode: 'fast' | 'thinking' | 'deep';
|
||||
runMode: 'fast' | 'thinking';
|
||||
quickMenuOpen: boolean;
|
||||
toolMenuOpen: boolean;
|
||||
modeMenuOpen: boolean;
|
||||
@ -1497,8 +1497,7 @@ const modelSlashMenuItems = computed<SlashMenuItem[]>(() => {
|
||||
const runModeSlashMenuItems = computed<SlashMenuItem[]>(() => {
|
||||
const modes: Array<{ value: string; label: string }> = [
|
||||
{ value: 'fast', label: '快速' },
|
||||
{ value: 'thinking', label: '思考' },
|
||||
{ value: 'deep', label: '深度' }
|
||||
{ value: 'thinking', label: '思考' }
|
||||
];
|
||||
const current = String(props.runMode || '');
|
||||
return modes.map((mode) => ({
|
||||
|
||||
@ -192,7 +192,7 @@ const props = defineProps<{
|
||||
iconStyle?: (key: string) => Record<string, string>;
|
||||
toolCategoryIcon: (categoryId: string) => string;
|
||||
modeMenuOpen: boolean;
|
||||
runMode?: 'fast' | 'thinking' | 'deep';
|
||||
runMode?: 'fast' | 'thinking';
|
||||
modelMenuOpen: boolean;
|
||||
modelOptions: Array<{
|
||||
key: string;
|
||||
@ -228,7 +228,7 @@ defineEmits<{
|
||||
(event: 'compress-conversation'): void;
|
||||
(event: 'toggle-approval-panel'): void;
|
||||
(event: 'toggle-mode-menu'): void;
|
||||
(event: 'select-run-mode', mode: 'fast' | 'thinking' | 'deep'): void;
|
||||
(event: 'select-run-mode', mode: 'fast' | 'thinking'): void;
|
||||
(event: 'toggle-model-menu'): void;
|
||||
(event: 'select-model', key: string): void;
|
||||
(event: 'open-review'): void;
|
||||
|
||||
@ -301,7 +301,7 @@ const props = defineProps<{
|
||||
isConnected: boolean;
|
||||
panelMenuOpen: boolean;
|
||||
panelMode: 'files' | 'todo' | 'subAgents' | 'backgroundCommands';
|
||||
runMode: 'fast' | 'thinking' | 'deep';
|
||||
runMode: 'fast' | 'thinking';
|
||||
fileManagerDisabled?: boolean;
|
||||
hostWorkspaceEnabled?: boolean;
|
||||
hostWorkspaces?: Array<{
|
||||
@ -341,17 +341,19 @@ const panelStyle = computed(() => {
|
||||
});
|
||||
|
||||
const resolveRunMode = () => {
|
||||
if (props.runMode === 'deep' || props.runMode === 'thinking' || props.runMode === 'fast') {
|
||||
return props.runMode;
|
||||
// 历史值 deep 映射为 thinking
|
||||
const rawMode = props.runMode as string;
|
||||
if (rawMode === 'deep' || rawMode === 'thinking') {
|
||||
return 'thinking';
|
||||
}
|
||||
if (rawMode === 'fast') {
|
||||
return 'fast';
|
||||
}
|
||||
return props.thinkingMode ? 'thinking' : 'fast';
|
||||
};
|
||||
|
||||
const modeIndicatorClass = computed(() => {
|
||||
const mode = resolveRunMode();
|
||||
if (mode === 'deep') {
|
||||
return 'deep';
|
||||
}
|
||||
if (mode === 'thinking') {
|
||||
return 'thinking';
|
||||
}
|
||||
@ -360,9 +362,6 @@ const modeIndicatorClass = computed(() => {
|
||||
|
||||
const modeIndicatorIcon = computed(() => {
|
||||
const mode = resolveRunMode();
|
||||
if (mode === 'deep') {
|
||||
return 'brainCog';
|
||||
}
|
||||
if (mode === 'thinking') {
|
||||
return 'brain';
|
||||
}
|
||||
@ -371,9 +370,6 @@ const modeIndicatorIcon = computed(() => {
|
||||
|
||||
const modeIndicatorTitle = computed(() => {
|
||||
const mode = resolveRunMode();
|
||||
if (mode === 'deep') {
|
||||
return '深度思考模式(点击切换)';
|
||||
}
|
||||
if (mode === 'thinking') {
|
||||
return '思考模式(点击切换)';
|
||||
}
|
||||
|
||||
@ -536,37 +536,41 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-list-row tall">
|
||||
<div class="settings-select-row">
|
||||
<span class="settings-row-copy"
|
||||
><span class="settings-row-title">思考频率</span
|
||||
><span class="settings-row-title">默认推理强度</span
|
||||
><span class="settings-row-desc"
|
||||
>连续快速模式运行多少轮后,系统强制切换到思考模型</span
|
||||
>思考模式下请求模型时附带的推理强度参数</span
|
||||
></span
|
||||
>
|
||||
<div class="settings-input-stack wide">
|
||||
<div class="settings-chip-row right">
|
||||
<div
|
||||
class="settings-select-wrap"
|
||||
:class="{ open: activeDropdown === 'reasoning-effort' }"
|
||||
@click.stop
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="settings-select-button"
|
||||
@click="toggleDropdown('reasoning-effort')"
|
||||
>
|
||||
{{ reasoningEffortLabel }}
|
||||
<span class="select-chevron" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div
|
||||
:class="['settings-floating-menu', { dark: activeTheme === 'dark' }]"
|
||||
:style="activeDropdown ? floatingMenuStyle : undefined"
|
||||
>
|
||||
<button
|
||||
v-for="preset in thinkingPresets"
|
||||
:key="preset.id"
|
||||
v-for="option in reasoningEffortOptions"
|
||||
:key="option.id"
|
||||
type="button"
|
||||
:class="{ active: isPresetActive(preset.value) }"
|
||||
@click.prevent="applyThinkingPreset(preset.value)"
|
||||
class="settings-menu-option"
|
||||
:class="{ selected: isEffortActive(option.value) }"
|
||||
@click="selectDefaultReasoningEffort(option.value)"
|
||||
>
|
||||
{{ preset.label }} · {{ preset.value }}轮
|
||||
</button>
|
||||
</div>
|
||||
<div class="settings-number-row">
|
||||
<input
|
||||
type="number"
|
||||
:min="thinkingIntervalRange.min"
|
||||
:max="thinkingIntervalRange.max"
|
||||
:placeholder="`默认 ${thinkingIntervalDefault} 轮`"
|
||||
:value="form.thinking_interval ?? ''"
|
||||
@input="handleThinkingInput"
|
||||
@focus="personalization.clearFeedback()"
|
||||
/>
|
||||
<button type="button" @click.prevent="restoreThinkingInterval">
|
||||
恢复默认
|
||||
<strong>{{ option.label }}</strong
|
||||
><span>{{ option.desc }}</span
|
||||
><svg viewBox="0 0 24 24"><path d="M5 12.5 9.5 17 19 7" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -1875,7 +1879,7 @@
|
||||
<div class="settings-select-row">
|
||||
<span class="settings-row-copy">
|
||||
<span class="settings-row-title">思考模式</span>
|
||||
<span class="settings-row-desc">fast = 快速响应,thinking = 深度推理</span>
|
||||
<span class="settings-row-desc">fast = 快速响应,thinking = 思考推理</span>
|
||||
</span>
|
||||
<div
|
||||
class="settings-select-wrap"
|
||||
@ -1908,7 +1912,7 @@
|
||||
:class="{ selected: roleForm.thinking_mode === 'thinking' }"
|
||||
@click="roleForm.thinking_mode = 'thinking'; closeDropdown()"
|
||||
>
|
||||
<strong>thinking</strong><span>深度推理模式</span><svg viewBox="0 0 24 24"><path d="M5 12.5 9.5 17 19 7" /></svg>
|
||||
<strong>thinking</strong><span>思考推理模式</span><svg viewBox="0 0 24 24"><path d="M5 12.5 9.5 17 19 7" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -2091,8 +2095,6 @@ const {
|
||||
toggleUpdating,
|
||||
toolCategories,
|
||||
skillsCatalog,
|
||||
thinkingIntervalDefault,
|
||||
thinkingIntervalRange,
|
||||
recentConversationsPromptLimitRange,
|
||||
experiments
|
||||
} = storeToRefs(personalization);
|
||||
@ -2374,7 +2376,8 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener('scroll', updateFloatingMenuPosition, true);
|
||||
});
|
||||
|
||||
type RunModeValue = 'fast' | 'thinking' | 'deep' | null;
|
||||
type RunModeValue = 'fast' | 'thinking' | null;
|
||||
type ReasoningEffortValue = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | null;
|
||||
type PermissionModeValue = 'readonly' | 'approval' | 'auto_approval' | 'unrestricted';
|
||||
type CompressionField =
|
||||
| 'shallow_compress_trigger_tokens'
|
||||
@ -2392,8 +2395,21 @@ const runModeOptions: Array<{
|
||||
badge?: string;
|
||||
}> = [
|
||||
{ id: 'fast', label: '快速模式', desc: '追求响应速度,跳过思考模型', value: 'fast' },
|
||||
{ id: 'thinking', label: '思考模式', desc: '首轮回复会先输出思考过程', value: 'thinking' },
|
||||
{ id: 'deep', label: '深度思考', desc: '整轮对话都使用思考模型', value: 'deep' }
|
||||
{ id: 'thinking', label: '思考模式', desc: '整轮对话都使用思考模型', value: 'thinking' }
|
||||
];
|
||||
|
||||
const reasoningEffortOptions: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
desc: string;
|
||||
value: ReasoningEffortValue;
|
||||
}> = [
|
||||
{ id: 'default', label: '默认', desc: '不指定强度,使用 API 默认行为', value: null },
|
||||
{ id: 'low', label: 'low', desc: '最低推理,响应最快', value: 'low' },
|
||||
{ id: 'medium', label: 'medium', desc: '较低推理', value: 'medium' },
|
||||
{ id: 'high', label: 'high', desc: '均衡推理', value: 'high' },
|
||||
{ id: 'xhigh', label: 'xhigh', desc: '更高推理', value: 'xhigh' },
|
||||
{ id: 'max', label: 'max', desc: '最高推理,适合最复杂任务', value: 'max' }
|
||||
];
|
||||
|
||||
const permissionModeOptions: Array<{
|
||||
@ -2422,20 +2438,14 @@ const filteredModelOptions = computed(() =>
|
||||
value: opt.key,
|
||||
label: opt.label,
|
||||
desc: opt.description || '',
|
||||
badge: multimodal === 'image,video' ? '图文' : opt.deepOnly ? '深度思考' : undefined,
|
||||
deepOnly: !!opt.deepOnly,
|
||||
badge: multimodal === 'image,video' ? '图文' : opt.thinkingOnly ? '思考' : undefined,
|
||||
thinkingOnly: !!opt.thinkingOnly,
|
||||
fastOnly: !!opt.fastOnly,
|
||||
disabled: policyStore.disabledModelSet.has(opt.key)
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const thinkingPresets = [
|
||||
{ id: 'low', label: '低', value: 10 },
|
||||
{ id: 'medium', label: '中', value: 5 },
|
||||
{ id: 'high', label: '高', value: 3 }
|
||||
];
|
||||
|
||||
const imageCompressionOptions = [
|
||||
{ id: 'original', label: '原图', desc: '不压缩' },
|
||||
{ id: '1080p', label: '1080p', desc: '最长边不超过 1080p 等比缩放' },
|
||||
@ -2454,6 +2464,24 @@ const runModeLabel = computed(() => {
|
||||
return runModeOptions.find((option) => isRunModeActive(option.value))?.label || '未设置';
|
||||
});
|
||||
|
||||
const isEffortActive = (value: ReasoningEffortValue) => {
|
||||
if (value === null) {
|
||||
return !form.value.default_reasoning_effort;
|
||||
}
|
||||
return form.value.default_reasoning_effort === value;
|
||||
};
|
||||
|
||||
const reasoningEffortLabel = computed(() => {
|
||||
return (
|
||||
reasoningEffortOptions.find((option) => isEffortActive(option.value))?.label || '默认'
|
||||
);
|
||||
});
|
||||
|
||||
const selectDefaultReasoningEffort = (value: ReasoningEffortValue) => {
|
||||
personalization.setDefaultReasoningEffort(value);
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const permissionModeLabel = computed(() => {
|
||||
return (
|
||||
permissionModeOptions.find((option) => option.id === form.value.default_permission_mode)
|
||||
@ -2965,10 +2993,6 @@ const startTutorial = () => {
|
||||
}, 320);
|
||||
};
|
||||
|
||||
const applyThinkingPreset = (value: number) => {
|
||||
personalization.setThinkingInterval(value);
|
||||
};
|
||||
|
||||
const isRunModeActive = (value: RunModeValue) => {
|
||||
if (value === null) {
|
||||
return !form.value.default_run_mode;
|
||||
@ -3053,8 +3077,8 @@ const selectBlockDisplayMode = (mode: 'traditional' | 'stacked' | 'minimal') =>
|
||||
const checkModeModelConflict = (mode: RunModeValue, model: string | null): boolean => {
|
||||
const found = (filteredModelOptions.value || []).find((item: any) => item.value === model);
|
||||
const warnings: string[] = [];
|
||||
if (found?.deepOnly && mode && mode !== 'deep') {
|
||||
warnings.push(`${found.label} 仅支持深度思考模式,已保持原设置。`);
|
||||
if (found?.thinkingOnly && mode && mode !== 'thinking') {
|
||||
warnings.push(`${found.label} 仅支持思考模式,已保持原设置。`);
|
||||
}
|
||||
if (found?.fastOnly && mode && mode !== 'fast') {
|
||||
warnings.push(`${found.label} 仅支持快速模式,已保持原设置。`);
|
||||
@ -3071,20 +3095,6 @@ const checkModeModelConflict = (mode: RunModeValue, model: string | null): boole
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleThinkingInput = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target.value) {
|
||||
personalization.setThinkingInterval(null);
|
||||
return;
|
||||
}
|
||||
const parsed = Number(target.value);
|
||||
personalization.setThinkingInterval(Number.isNaN(parsed) ? null : parsed);
|
||||
};
|
||||
|
||||
const restoreThinkingInterval = () => {
|
||||
personalization.setThinkingInterval(null);
|
||||
};
|
||||
|
||||
const handleRecentConversationsPromptLimitInput = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement | null;
|
||||
if (!target) {
|
||||
@ -3132,16 +3142,6 @@ const restoreCompressionDefaults = () => {
|
||||
personalization.updateField({ key: 'deep_compress_trigger_tokens', value: null });
|
||||
};
|
||||
|
||||
const isPresetActive = (value: number) => {
|
||||
if (
|
||||
form.value.thinking_interval === null ||
|
||||
typeof form.value.thinking_interval === 'undefined'
|
||||
) {
|
||||
return value === thinkingIntervalDefault.value;
|
||||
}
|
||||
return form.value.thinking_interval === value;
|
||||
};
|
||||
|
||||
const toggleCategory = (categoryId: string) => {
|
||||
personalization.toggleDefaultToolCategory(categoryId);
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import type { Socket } from 'socket.io-client';
|
||||
import type { ReasoningEffort } from './personalization';
|
||||
|
||||
interface ConnectionState {
|
||||
isConnected: boolean;
|
||||
@ -8,7 +9,8 @@ interface ConnectionState {
|
||||
projectPath: string;
|
||||
agentVersion: string;
|
||||
thinkingMode: boolean;
|
||||
runMode: 'fast' | 'thinking' | 'deep';
|
||||
runMode: 'fast' | 'thinking';
|
||||
reasoningEffort: ReasoningEffort | null;
|
||||
}
|
||||
|
||||
export const useConnectionStore = defineStore('connection', {
|
||||
@ -19,7 +21,8 @@ export const useConnectionStore = defineStore('connection', {
|
||||
projectPath: '',
|
||||
agentVersion: '',
|
||||
thinkingMode: true,
|
||||
runMode: 'thinking'
|
||||
runMode: 'thinking',
|
||||
reasoningEffort: null
|
||||
}),
|
||||
actions: {
|
||||
setSocket(socket: Socket | null) {
|
||||
@ -49,9 +52,12 @@ export const useConnectionStore = defineStore('connection', {
|
||||
toggleThinkingMode() {
|
||||
this.thinkingMode = !this.thinkingMode;
|
||||
},
|
||||
setRunMode(mode: 'fast' | 'thinking' | 'deep') {
|
||||
setRunMode(mode: 'fast' | 'thinking') {
|
||||
this.runMode = mode;
|
||||
this.thinkingMode = mode !== 'fast';
|
||||
},
|
||||
setReasoningEffort(effort: ReasoningEffort | null) {
|
||||
this.reasoningEffort = effort;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -14,7 +14,8 @@ export interface ModelOption {
|
||||
maxOutputTokens?: number | null;
|
||||
fastOnly: boolean;
|
||||
supportsThinking: boolean;
|
||||
deepOnly?: boolean;
|
||||
thinkingOnly?: boolean;
|
||||
supportsReasoningEffort?: boolean;
|
||||
}
|
||||
|
||||
interface ModelState {
|
||||
@ -89,7 +90,8 @@ export const useModelStore = defineStore('model', {
|
||||
typeof item.max_output_tokens === 'number' ? item.max_output_tokens : null,
|
||||
fastOnly: !!item.fast_only,
|
||||
supportsThinking: !!item.supports_thinking,
|
||||
deepOnly: !!item.deep_only
|
||||
thinkingOnly: !!item.thinking_only,
|
||||
supportsReasoningEffort: !!item.supports_reasoning_effort
|
||||
};
|
||||
});
|
||||
this.setModels(mapped);
|
||||
|
||||
@ -4,7 +4,8 @@ import { persistDefaultHideWorkspace, useUiStore } from './ui';
|
||||
|
||||
export type BlockDisplayMode = 'traditional' | 'stacked' | 'minimal';
|
||||
export type CompactMessageDisplay = 'full' | 'brief';
|
||||
type RunMode = 'fast' | 'thinking' | 'deep';
|
||||
type RunMode = 'fast' | 'thinking';
|
||||
export type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
||||
type PermissionMode = 'readonly' | 'approval' | 'auto_approval' | 'unrestricted';
|
||||
type CommunicationStyle = 'default' | 'human_like' | 'auto';
|
||||
type ConversationContinuity = 'low' | 'medium' | 'high';
|
||||
@ -40,9 +41,9 @@ interface PersonalForm {
|
||||
profession: string;
|
||||
tone: string;
|
||||
considerations: string;
|
||||
thinking_interval: number | null;
|
||||
disabled_tool_categories: string[];
|
||||
default_run_mode: RunMode | null;
|
||||
default_reasoning_effort: ReasoningEffort | null;
|
||||
default_permission_mode: PermissionMode;
|
||||
versioning_enabled_by_default: boolean;
|
||||
versioning_backup_mode: VersioningBackupMode;
|
||||
@ -89,19 +90,16 @@ interface PersonalizationState {
|
||||
form: PersonalForm;
|
||||
toolCategories: Array<{ id: string; label: string }>;
|
||||
skillsCatalog: Array<{ id: string; label: string; description?: string }>;
|
||||
thinkingIntervalDefault: number;
|
||||
thinkingIntervalRange: { min: number; max: number };
|
||||
recentConversationsPromptLimitRange: { min: number; max: number };
|
||||
experiments: ExperimentState;
|
||||
}
|
||||
|
||||
const DEFAULT_INTERVAL = 10;
|
||||
const DEFAULT_INTERVAL_RANGE = { min: 1, max: 50 };
|
||||
const DEFAULT_RECENT_CONVERSATIONS_PROMPT_LIMIT = 10;
|
||||
const DEFAULT_RECENT_CONVERSATIONS_PROMPT_LIMIT_RANGE = { min: 1, max: 30 };
|
||||
const DEFAULT_SHALLOW_COMPRESS_TRIGGER_TOKENS = 80000;
|
||||
const DEFAULT_DEEP_COMPRESS_TRIGGER_TOKENS = 150000;
|
||||
const RUN_MODE_OPTIONS: RunMode[] = ['fast', 'thinking', 'deep'];
|
||||
const RUN_MODE_OPTIONS: RunMode[] = ['fast', 'thinking'];
|
||||
const REASONING_EFFORT_OPTIONS: ReasoningEffort[] = ['low', 'medium', 'high', 'xhigh', 'max'];
|
||||
const EXPERIMENT_STORAGE_KEY = 'agents_personalization_experiments';
|
||||
const THEME_STORAGE_KEY = 'agents_ui_theme';
|
||||
const STACKED_HIDE_BORDERS_STORAGE_KEY = 'agents_stacked_hide_borders';
|
||||
@ -204,9 +202,9 @@ const defaultForm = (): PersonalForm => ({
|
||||
profession: '',
|
||||
tone: '',
|
||||
considerations: '',
|
||||
thinking_interval: null,
|
||||
disabled_tool_categories: [],
|
||||
default_run_mode: null,
|
||||
default_reasoning_effort: null,
|
||||
default_permission_mode: 'unrestricted',
|
||||
versioning_enabled_by_default: true,
|
||||
versioning_backup_mode: 'shallow',
|
||||
@ -299,8 +297,6 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
form: defaultForm(),
|
||||
toolCategories: [],
|
||||
skillsCatalog: [],
|
||||
thinkingIntervalDefault: DEFAULT_INTERVAL,
|
||||
thinkingIntervalRange: { ...DEFAULT_INTERVAL_RANGE },
|
||||
recentConversationsPromptLimitRange: { ...DEFAULT_RECENT_CONVERSATIONS_PROMPT_LIMIT_RANGE },
|
||||
experiments: loadExperimentState()
|
||||
}),
|
||||
@ -414,15 +410,20 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
: Array.isArray(data.considerations)
|
||||
? data.considerations.filter((item: unknown) => typeof item === 'string').join('\n')
|
||||
: '',
|
||||
thinking_interval:
|
||||
typeof data.thinking_interval === 'number' ? data.thinking_interval : null,
|
||||
disabled_tool_categories: Array.isArray(data.disabled_tool_categories)
|
||||
? data.disabled_tool_categories.filter((item: unknown) => typeof item === 'string')
|
||||
: [],
|
||||
default_run_mode:
|
||||
typeof data.default_run_mode === 'string' &&
|
||||
RUN_MODE_OPTIONS.includes(data.default_run_mode as RunMode)
|
||||
? (data.default_run_mode as RunMode)
|
||||
default_run_mode: (() => {
|
||||
// 历史值 deep 映射为 thinking
|
||||
const saved = data.default_run_mode === 'deep' ? 'thinking' : data.default_run_mode;
|
||||
return typeof saved === 'string' && RUN_MODE_OPTIONS.includes(saved as RunMode)
|
||||
? (saved as RunMode)
|
||||
: null;
|
||||
})(),
|
||||
default_reasoning_effort:
|
||||
typeof data.default_reasoning_effort === 'string' &&
|
||||
REASONING_EFFORT_OPTIONS.includes(data.default_reasoning_effort as ReasoningEffort)
|
||||
? (data.default_reasoning_effort as ReasoningEffort)
|
||||
: null,
|
||||
default_permission_mode:
|
||||
data.default_permission_mode === 'readonly' ||
|
||||
@ -553,20 +554,6 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
return Math.max(min, Math.min(max, Math.round(parsed)));
|
||||
},
|
||||
applyPersonalizationMeta(payload: any) {
|
||||
if (payload && typeof payload.thinking_interval_default === 'number') {
|
||||
this.thinkingIntervalDefault = payload.thinking_interval_default;
|
||||
} else {
|
||||
this.thinkingIntervalDefault = DEFAULT_INTERVAL;
|
||||
}
|
||||
if (payload && payload.thinking_interval_range) {
|
||||
const { min, max } = payload.thinking_interval_range;
|
||||
this.thinkingIntervalRange = {
|
||||
min: typeof min === 'number' ? min : DEFAULT_INTERVAL_RANGE.min,
|
||||
max: typeof max === 'number' ? max : DEFAULT_INTERVAL_RANGE.max
|
||||
};
|
||||
} else {
|
||||
this.thinkingIntervalRange = { ...DEFAULT_INTERVAL_RANGE };
|
||||
}
|
||||
if (payload && payload.recent_conversations_prompt_limit_range) {
|
||||
const { min, max } = payload.recent_conversations_prompt_limit_range;
|
||||
this.recentConversationsPromptLimitRange = {
|
||||
@ -736,28 +723,6 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
setThinkingInterval(value: number | null) {
|
||||
let target: number | null = value;
|
||||
if (typeof target === 'number') {
|
||||
if (Number.isNaN(target)) {
|
||||
target = null;
|
||||
} else {
|
||||
const rounded = Math.round(target);
|
||||
const min = this.thinkingIntervalRange.min ?? DEFAULT_INTERVAL_RANGE.min;
|
||||
const max = this.thinkingIntervalRange.max ?? DEFAULT_INTERVAL_RANGE.max;
|
||||
target = Math.max(min, Math.min(max, rounded));
|
||||
if (target === this.thinkingIntervalDefault) {
|
||||
target = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.form = {
|
||||
...this.form,
|
||||
thinking_interval: target
|
||||
};
|
||||
this.clearFeedback();
|
||||
this.scheduleAutoSave();
|
||||
},
|
||||
setRecentConversationsPromptLimit(value: number | null) {
|
||||
const target =
|
||||
value === null || typeof value === 'undefined'
|
||||
@ -816,6 +781,18 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
this.clearFeedback();
|
||||
this.scheduleAutoSave();
|
||||
},
|
||||
setDefaultReasoningEffort(effort: ReasoningEffort | null) {
|
||||
let target: ReasoningEffort | null = null;
|
||||
if (typeof effort === 'string' && REASONING_EFFORT_OPTIONS.includes(effort as ReasoningEffort)) {
|
||||
target = effort as ReasoningEffort;
|
||||
}
|
||||
this.form = {
|
||||
...this.form,
|
||||
default_reasoning_effort: target
|
||||
};
|
||||
this.clearFeedback();
|
||||
this.scheduleAutoSave();
|
||||
},
|
||||
setDefaultPermissionMode(mode: PermissionMode) {
|
||||
const allowed: PermissionMode[] = ['readonly', 'approval', 'auto_approval', 'unrestricted'];
|
||||
const target = allowed.includes(mode) ? mode : 'unrestricted';
|
||||
|
||||
@ -66,7 +66,7 @@ export const useTaskStore = defineStore('task', {
|
||||
conversationId: string | null = null,
|
||||
options: {
|
||||
model_key?: string | null;
|
||||
run_mode?: 'fast' | 'thinking' | 'deep' | null;
|
||||
run_mode?: 'fast' | 'thinking' | null;
|
||||
thinking_mode?: boolean | null;
|
||||
message_source?: string | null;
|
||||
goal_mode?: boolean | null;
|
||||
|
||||
@ -104,7 +104,7 @@ const DEFAULT_STEPS: TutorialStep[] = [
|
||||
{
|
||||
id: 'workspace-mode-indicator',
|
||||
title: '思考模式',
|
||||
description: '点击切换快速 / 思考 / 深度思考。',
|
||||
description: '点击切换快速 / 思考。',
|
||||
target: '[data-tutorial="run-mode-indicator"]',
|
||||
mode: 'info',
|
||||
placement: 'right',
|
||||
@ -141,7 +141,7 @@ const DEFAULT_STEPS: TutorialStep[] = [
|
||||
{
|
||||
id: 'header-runmode-options',
|
||||
title: '运行模式列表',
|
||||
description: '这里可切换快速 / 思考 / 深度思考。',
|
||||
description: '这里可切换快速 / 思考。',
|
||||
target: '[data-tutorial="header-runmode-options"]',
|
||||
mode: 'info',
|
||||
placement: 'bottom',
|
||||
@ -278,7 +278,7 @@ const DEFAULT_STEPS: TutorialStep[] = [
|
||||
{
|
||||
id: 'mobile-runmode-options',
|
||||
title: '思考模式列表',
|
||||
description: '这里可切换快速 / 思考 / 深度思考。',
|
||||
description: '这里可切换快速 / 思考。',
|
||||
target: '[data-tutorial="header-runmode-options"]',
|
||||
mode: 'info',
|
||||
placement: 'bottom',
|
||||
|
||||
@ -28,6 +28,24 @@
|
||||
/* 拖拽预览浮层投影(主题无关,固定黑投影) */
|
||||
--drag-preview-shadow: rgba(0, 0, 0, 0.35);
|
||||
|
||||
/* 推理强度档位色(主题无关,固定功能指示色):EffortSlider 滑块填充与动效 */
|
||||
--effort-low: #f5c542;
|
||||
--effort-medium: #3fd07c;
|
||||
--effort-high: #4f8ef7;
|
||||
--effort-xhigh: #a55df0;
|
||||
--effort-xhigh-shine: rgba(255, 255, 255, 0.55);
|
||||
--effort-max-red: #ff3b5c;
|
||||
--effort-max-orange: #ff9f2e;
|
||||
--effort-max-yellow: #ffe93b;
|
||||
--effort-max-green: #3ddc68;
|
||||
--effort-max-cyan: #2fd4ff;
|
||||
--effort-max-purple: #7a5cff;
|
||||
--effort-max-pink: #ff4fd8;
|
||||
--effort-knob-bg: #ffffff;
|
||||
--effort-knob-shadow: rgba(0, 0, 0, 0.28);
|
||||
--effort-dot-covered: rgba(255, 255, 255, 0.55);
|
||||
--effort-dot-uncovered: rgba(0, 0, 0, 0.28);
|
||||
|
||||
/* === 兼容别名(旧 token 名 → 新语义 token),迁移完成后删除 === */
|
||||
--claude-bg: var(--surface-base);
|
||||
--claude-panel: var(--surface-panel);
|
||||
@ -76,6 +94,7 @@
|
||||
:root[data-theme='classic'] {
|
||||
color-scheme: light;
|
||||
/* Surface 表面层级 —— Claude 亮色暖奶油盘(canvas<soft<card<cream-strong,raised纯白浮起) */
|
||||
--effort-track-bg: var(--surface-muted);
|
||||
--surface-base: #faf9f5;
|
||||
--surface-panel: #faf9f5;
|
||||
--surface-rail: #f5f0e8;
|
||||
@ -136,6 +155,7 @@
|
||||
|
||||
:root[data-theme='light'] {
|
||||
color-scheme: light;
|
||||
--effort-track-bg: var(--surface-muted);
|
||||
/* Surface 表面层级 —— ChatGPT 亮色冷白盘 */
|
||||
--surface-base: #ffffff;
|
||||
--surface-panel: #ffffff;
|
||||
@ -197,6 +217,8 @@
|
||||
|
||||
:root[data-theme='dark'] {
|
||||
color-scheme: dark;
|
||||
/* 推理强度滑槽(深色下需可见灰,不用近黑 surface-muted) */
|
||||
--effort-track-bg: #3a3a3c;
|
||||
/* Surface 表面层级 */
|
||||
--surface-base: #1a1a1a;
|
||||
--surface-panel: #1a1a1a;
|
||||
@ -258,6 +280,7 @@
|
||||
|
||||
/* 首屏(JS 设置 data-theme 之前)回退到 classic 取值 */
|
||||
:root:not([data-theme]) {
|
||||
--effort-track-bg: var(--surface-muted);
|
||||
/* Surface 表面层级 —— 同 classic(Claude 亮色暖奶油盘) */
|
||||
--surface-base: #faf9f5;
|
||||
--surface-panel: #faf9f5;
|
||||
|
||||
@ -298,14 +298,14 @@
|
||||
top: 48px;
|
||||
left: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(160px, 0.65fr);
|
||||
grid-template-columns: minmax(0, 300px) minmax(220px, 1fr);
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
border-radius: 14px;
|
||||
background: var(--surface-soft);
|
||||
box-shadow: none;
|
||||
pointer-events: auto;
|
||||
width: min(640px, calc(100vw - 32px));
|
||||
width: min(720px, calc(100vw - 32px));
|
||||
max-width: calc(100vw - 32px);
|
||||
z-index: 50;
|
||||
}
|
||||
@ -337,6 +337,13 @@
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* 运行模式列内的推理强度滑块:与上方选项用分隔线区分 */
|
||||
.dropdown-effort {
|
||||
margin-top: 8px;
|
||||
padding: 10px 4px 2px;
|
||||
border-top: 1px solid var(--border-default);
|
||||
}
|
||||
|
||||
.dropdown-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -16,23 +16,6 @@ class ServerRefactorSmokeTest(unittest.TestCase):
|
||||
def test_chat_flow_helper_functions(self):
|
||||
import server.chat_flow_helpers as helpers
|
||||
|
||||
terminal = SimpleNamespace(
|
||||
deep_thinking_mode=False,
|
||||
thinking_mode=True,
|
||||
api_client=SimpleNamespace(
|
||||
force_thinking_next_call=False,
|
||||
skip_thinking_next_call=False,
|
||||
last_call_used_thinking=False,
|
||||
),
|
||||
thinking_fast_interval=2,
|
||||
)
|
||||
|
||||
helpers.apply_thinking_schedule(terminal, default_interval=2, debug_logger=lambda _: None)
|
||||
helpers.update_thinking_after_call(terminal, debug_logger=lambda _: None)
|
||||
state = helpers.get_thinking_state(terminal)
|
||||
|
||||
self.assertIsInstance(state, dict)
|
||||
self.assertIn("fast_streak", state)
|
||||
self.assertTrue(helpers.detect_tool_failure({"success": False}))
|
||||
self.assertFalse(helpers.detect_tool_failure({"success": True}))
|
||||
|
||||
|
||||
@ -58,9 +58,7 @@ class DeepSeekClientBaseMixin:
|
||||
self.current_context_tokens: int = 0
|
||||
self.max_context_tokens: Optional[int] = None
|
||||
self.default_context_window: Optional[int] = None
|
||||
self.thinking_mode = thinking_mode # True=智能思考模式, False=快速模式
|
||||
self.deep_thinking_mode = False # 深度思考模式:整轮都使用思考模型
|
||||
self.deep_thinking_session = False # 当前任务是否处于深度思考会话
|
||||
self.thinking_mode = thinking_mode # True=思考模式(每次请求都用思考配置), False=快速模式
|
||||
self.web_mode = web_mode # Web模式标志,用于禁用print输出
|
||||
# 兼容旧代码路径
|
||||
self.api_base_url = self.fast_api_config["base_url"]
|
||||
@ -69,12 +67,9 @@ class DeepSeekClientBaseMixin:
|
||||
self.model_key = None # 由宿主终端注入,便于做模型兼容处理
|
||||
self.model_multimodal = "none" # 由模型 profile 注入,model_key 不可用时用于能力兜底
|
||||
self.project_path: Optional[str] = None
|
||||
# 每个任务的独立状态
|
||||
self.current_task_first_call = True # 当前任务是否是第一次调用
|
||||
self.current_task_thinking = "" # 当前任务的思考内容
|
||||
self.force_thinking_next_call = False # 单次强制思考
|
||||
self.skip_thinking_next_call = False # 单次强制快速
|
||||
self.last_call_used_thinking = False # 最近一次调用是否使用思考模型
|
||||
# 推理强度(reasoning effort):None=默认(不传参);由会话级设置注入
|
||||
self.reasoning_effort: Optional[str] = None
|
||||
self.supports_reasoning_effort = False # 当前模型是否支持推理强度,由 apply_profile 注入
|
||||
# 最近一次API错误详情
|
||||
self.last_error_info: Optional[Dict[str, Any]] = None
|
||||
# 请求体落盘目录(跟随 LOGS_DIR,默认 ~/.astrion/<mode>/logs/api_requests)
|
||||
|
||||
@ -59,22 +59,11 @@ class DeepSeekClientChatMixin:
|
||||
self._print(f"{OUTPUT_FORMATS['error']} API密钥未配置,请检查模型配置")
|
||||
return
|
||||
|
||||
# 决定是否使用思考模式
|
||||
# 决定是否使用思考模式(思考模式下每次请求都用思考配置)
|
||||
current_thinking_mode = self.get_current_thinking_mode()
|
||||
api_config = self._select_api_config(current_thinking_mode)
|
||||
headers = self._build_headers(api_config["api_key"])
|
||||
|
||||
# 如果当前为快速模式但已有思考内容,提示沿用
|
||||
if self.thinking_mode and not current_thinking_mode and self.current_task_thinking:
|
||||
self._print(f"{OUTPUT_FORMATS['info']} [任务内快速模式] 使用本次任务的思考继续处理...")
|
||||
|
||||
# 记录本次调用的模式
|
||||
self.last_call_used_thinking = current_thinking_mode
|
||||
if current_thinking_mode and self.force_thinking_next_call:
|
||||
self.force_thinking_next_call = False
|
||||
if not current_thinking_mode and self.skip_thinking_next_call:
|
||||
self.skip_thinking_next_call = False
|
||||
|
||||
try:
|
||||
override_max = self.thinking_max_tokens if current_thinking_mode else self.fast_max_tokens
|
||||
if override_max is not None:
|
||||
@ -110,6 +99,9 @@ class DeepSeekClientChatMixin:
|
||||
extra_params = self.thinking_extra_params if current_thinking_mode else self.fast_extra_params
|
||||
if extra_params:
|
||||
payload.update(extra_params)
|
||||
# 推理强度:思考模式下且模型支持时注入(与额外参数同级的平铺参数)
|
||||
if current_thinking_mode and self.supports_reasoning_effort and self.reasoning_effort:
|
||||
payload["reasoning_effort"] = self.reasoning_effort
|
||||
if tools:
|
||||
payload["tools"] = tools
|
||||
payload["tool_choice"] = "auto"
|
||||
@ -120,9 +112,8 @@ class DeepSeekClientChatMixin:
|
||||
"event": "request_prepare",
|
||||
"model_key": self.model_key,
|
||||
"thinking_mode_flag": bool(self.thinking_mode),
|
||||
"deep_thinking_mode": bool(self.deep_thinking_mode),
|
||||
"deep_thinking_session": bool(self.deep_thinking_session),
|
||||
"current_call_use_thinking": bool(current_thinking_mode),
|
||||
"reasoning_effort": self.reasoning_effort,
|
||||
"api_base_url": api_config.get("base_url"),
|
||||
"api_model_id": api_config.get("model_id"),
|
||||
"payload_model": payload.get("model"),
|
||||
@ -451,12 +442,6 @@ class DeepSeekClientChatMixin:
|
||||
if in_thinking:
|
||||
self._print("\n💭 [思考结束]\n")
|
||||
|
||||
# 记录思考内容并更新调用状态
|
||||
if self.last_call_used_thinking and current_thinking:
|
||||
self.current_task_thinking = current_thinking
|
||||
if self.current_task_first_call:
|
||||
self.current_task_first_call = False # 标记当前任务的第一次调用已完成
|
||||
|
||||
# 如果没有工具调用,说明完成了
|
||||
if not tool_calls:
|
||||
if full_response: # 有正常回复,任务完成
|
||||
@ -602,24 +587,6 @@ class DeepSeekClientChatMixin:
|
||||
thinking_content = ""
|
||||
in_thinking = False
|
||||
|
||||
# 如果思考模式且已有本任务的思考内容,补充到上下文,确保多次调用时思考不割裂
|
||||
if (
|
||||
self.thinking_mode
|
||||
and not self.current_task_first_call
|
||||
and self.current_task_thinking
|
||||
):
|
||||
thinking_context = (
|
||||
"\n=== 📋 本次任务的思考 ===\n"
|
||||
f"{self.current_task_thinking}\n"
|
||||
"=== 思考结束 ===\n"
|
||||
"提示:以上是本轮任务先前的思考,请在此基础上继续。"
|
||||
)
|
||||
messages.append({
|
||||
"role": "system",
|
||||
"content": thinking_context
|
||||
})
|
||||
thinking_context_injected = True
|
||||
|
||||
try:
|
||||
async for chunk in self.chat(messages, tools=None, stream=True):
|
||||
if chunk.get("error"):
|
||||
@ -660,11 +627,6 @@ class DeepSeekClientChatMixin:
|
||||
if in_thinking:
|
||||
self._print("\n💭 [思考结束]\n")
|
||||
|
||||
if self.last_call_used_thinking and thinking_content:
|
||||
self.current_task_thinking = thinking_content
|
||||
if self.current_task_first_call:
|
||||
self.current_task_first_call = False
|
||||
|
||||
# 如果没有收到任何响应
|
||||
if not full_response and not thinking_content:
|
||||
self._print(f"{OUTPUT_FORMATS['error']} API未返回任何内容,请检查API密钥和模型ID")
|
||||
|
||||
@ -66,6 +66,7 @@ class DeepSeekClientProfileMixin:
|
||||
self.thinking_max_tokens = thinking.get("max_tokens")
|
||||
self.fast_extra_params = fast.get("extra_params") or {}
|
||||
self.thinking_extra_params = thinking.get("extra_params") or {}
|
||||
self.supports_reasoning_effort = bool(profile.get("supports_reasoning_effort"))
|
||||
self.model_multimodal = self._normalize_multimodal_capability(profile.get("multimodal"))
|
||||
self.default_context_window = profile.get("context_window") or fast.get("context_window")
|
||||
# 同步旧字段
|
||||
@ -101,31 +102,8 @@ class DeepSeekClientProfileMixin:
|
||||
self.max_context_tokens = None
|
||||
|
||||
def get_current_thinking_mode(self) -> bool:
|
||||
"""获取当前应该使用的思考模式"""
|
||||
if self.deep_thinking_session:
|
||||
return True
|
||||
if not self.thinking_mode:
|
||||
return False
|
||||
if self.force_thinking_next_call:
|
||||
return True
|
||||
if self.skip_thinking_next_call:
|
||||
return False
|
||||
return self.current_task_first_call
|
||||
|
||||
def set_deep_thinking_mode(self, enabled: bool):
|
||||
"""配置深度思考模式(持续使用思考模型)。"""
|
||||
self.deep_thinking_mode = bool(enabled)
|
||||
if not enabled:
|
||||
self.deep_thinking_session = False
|
||||
|
||||
def start_new_task(self, force_deep: bool = False):
|
||||
"""开始新任务(重置任务级别的状态)"""
|
||||
self.current_task_first_call = True
|
||||
self.current_task_thinking = ""
|
||||
self.force_thinking_next_call = False
|
||||
self.skip_thinking_next_call = False
|
||||
self.last_call_used_thinking = False
|
||||
self.deep_thinking_session = bool(force_deep) or bool(self.deep_thinking_mode)
|
||||
"""获取当前应该使用的思考模式:思考模式下每次请求都用思考配置。"""
|
||||
return bool(self.thinking_mode)
|
||||
|
||||
def _build_headers(self, api_key: str) -> Dict[str, str]:
|
||||
return {
|
||||
|
||||
@ -30,6 +30,10 @@ except Exception:
|
||||
_CONV_SAVE_LOG_DIR = None
|
||||
|
||||
|
||||
# save_conversation 专用哨兵:区分「不更新该字段」与「显式清除为 None」
|
||||
_KEEP = object()
|
||||
|
||||
|
||||
def _debug_conversation_save_trace(conversation_id: str, file_path, data: Dict):
|
||||
"""[临时调试] 记录每次对话文件写入,重点捕获「非空被空覆盖」的调用栈。
|
||||
|
||||
@ -362,6 +366,7 @@ class CrudMixin:
|
||||
project_path: str = None,
|
||||
thinking_mode: bool = None,
|
||||
run_mode: Optional[str] = None,
|
||||
reasoning_effort: Any = _KEEP,
|
||||
todo_list: Optional[Dict] = None,
|
||||
model_key: Optional[str] = None,
|
||||
has_images: Optional[bool] = None,
|
||||
@ -436,12 +441,23 @@ class CrudMixin:
|
||||
if thinking_mode is not None:
|
||||
existing_data["metadata"]["thinking_mode"] = thinking_mode
|
||||
if run_mode:
|
||||
normalized_mode = run_mode if run_mode in {"fast", "thinking", "deep"} else (
|
||||
"thinking" if existing_data["metadata"].get("thinking_mode") else "fast"
|
||||
)
|
||||
normalized_mode = str(run_mode).lower()
|
||||
if normalized_mode == "deep": # 旧版标识符映射
|
||||
normalized_mode = "thinking"
|
||||
if normalized_mode not in {"fast", "thinking"}:
|
||||
normalized_mode = "thinking" if existing_data["metadata"].get("thinking_mode") else "fast"
|
||||
existing_data["metadata"]["run_mode"] = normalized_mode
|
||||
elif "run_mode" not in existing_data["metadata"]:
|
||||
existing_data["metadata"]["run_mode"] = "thinking" if existing_data["metadata"].get("thinking_mode") else "fast"
|
||||
if reasoning_effort is not _KEEP:
|
||||
# 显式传入:None/空 = 清除为默认(不传参);否则写入档位
|
||||
if reasoning_effort is None:
|
||||
existing_data["metadata"]["reasoning_effort"] = None
|
||||
else:
|
||||
normalized_effort = str(reasoning_effort).strip().lower()
|
||||
existing_data["metadata"]["reasoning_effort"] = normalized_effort or None
|
||||
elif "reasoning_effort" not in existing_data["metadata"]:
|
||||
existing_data["metadata"]["reasoning_effort"] = None
|
||||
# 推断最新使用的模型(优先参数,其次倒序扫描助手消息)
|
||||
inferred_model = None
|
||||
if model_key is None:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user