diff --git a/.DS_Store b/.DS_Store index ceb2fb9..2948516 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/core/main_terminal.py b/core/main_terminal.py index b99839c..9455c49 100644 --- a/core/main_terminal.py +++ b/core/main_terminal.py @@ -367,6 +367,14 @@ class MainTerminal: self.tool_category_states[key] = False if key in disabled_categories else category.default_enabled self._refresh_disabled_tools() + # 默认模型偏好(优先应用,再处理运行模式) + preferred_model = effective_config.get("default_model") + if 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): normalized_mode = preferred_mode.strip().lower() diff --git a/core/web_terminal.py b/core/web_terminal.py index 5e96ae0..cb93938 100644 --- a/core/web_terminal.py +++ b/core/web_terminal.py @@ -2,8 +2,10 @@ import json from typing import Dict, List, Optional, Callable, TYPE_CHECKING +import os from core.main_terminal import MainTerminal from utils.logger import setup_logger +from modules.personalization_manager import load_personalization_config try: from config import MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE except ImportError: @@ -68,8 +70,8 @@ class WebTerminal(MainTerminal): self.message_callback = message_callback self.web_mode = True - # 设置API客户端为Web模式(禁用print) - self.api_client.web_mode = True + # 默认允许输出(api_client.web_mode=False 表示允许 _print),若需静默可设置 WEB_API_SILENT=1 + self.api_client.web_mode = bool(os.environ.get("WEB_API_SILENT")) # 重新初始化终端管理器 self.terminal_manager = TerminalManager( @@ -107,7 +109,34 @@ class WebTerminal(MainTerminal): Returns: Dict: 包含新对话信息 """ - if thinking_mode is None: + prefer_defaults = thinking_mode is None and run_mode is None + thinking_mode_explicit = thinking_mode is not None + if prefer_defaults: + # 优先回到个性化/默认配置;没有配置时退回快速模式 + 默认模型 + try: + prefs = load_personalization_config(self.data_dir) + except Exception as exc: + prefs = {} + logger.warning("加载个性化偏好失败,将使用内置默认: %s", exc) + + # 新对话视为“干净”会话,清除图片限制便于切换模型 + self.context_manager.has_images = False + + preferred_model = prefs.get("default_model") or "kimi" + try: + self.set_model(preferred_model) + except Exception as exc: + logger.warning("忽略无效默认模型 %s: %s", preferred_model, exc) + + preferred_mode = prefs.get("default_run_mode") + if isinstance(preferred_mode, str) and preferred_mode.lower() in {"fast", "thinking", "deep"}: + try: + self.set_run_mode(preferred_mode.lower()) + except ValueError as exc: + logger.warning("忽略无效默认运行模式 %s: %s", preferred_mode, exc) + else: + # 未配置默认模式时回到快速模式 + self.set_run_mode("fast") thinking_mode = self.thinking_mode if isinstance(run_mode, str): @@ -116,6 +145,15 @@ class WebTerminal(MainTerminal): thinking_mode = self.thinking_mode except ValueError: logger.warning("无效的 run_mode 参数: %s", run_mode) + elif thinking_mode_explicit: + # run_mode 未显式传入但思考开关给定时,基于布尔值决定模式 + try: + self.set_run_mode("thinking" if bool(thinking_mode) else "fast") + except ValueError: + pass + + if thinking_mode is None: + thinking_mode = self.thinking_mode try: conversation_id = self.context_manager.start_new_conversation( diff --git a/modules/personalization_manager.py b/modules/personalization_manager.py index ffcb54e..9be544e 100644 --- a/modules/personalization_manager.py +++ b/modules/personalization_manager.py @@ -36,6 +36,7 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = { "default_run_mode": None, "auto_generate_title": True, "tool_intent_enabled": True, + "default_model": "kimi", } __all__ = [ @@ -84,8 +85,13 @@ def load_personalization_config(base_dir: PathLike) -> Dict[str, Any]: return ensure_personalization_config(base_dir) try: with open(path, "r", encoding="utf-8") as f: - data = json.load(f) - return sanitize_personalization_payload(data) + raw = json.load(f) + sanitized = sanitize_personalization_payload(raw) + # 若发现缺失字段(如默认模型)或数据被规范化,主动写回文件,避免下一次读取仍为旧格式 + if sanitized != raw: + with open(path, "w", encoding="utf-8") as wf: + json.dump(sanitized, wf, ensure_ascii=False, indent=2) + return sanitized except (json.JSONDecodeError, OSError): # 重置为默认配置,避免错误阻塞 with open(path, "w", encoding="utf-8") as f: @@ -103,6 +109,7 @@ def sanitize_personalization_payload( base.update(fallback) data = payload or {} allowed_tool_categories = set(TOOL_CATEGORIES.keys()) + allowed_models = {"kimi", "deepseek", "qwen3-max", "qwen3-vl-plus"} def _resolve_short_field(key: str) -> str: if key in data: @@ -140,6 +147,14 @@ def sanitize_personalization_payload( base["default_run_mode"] = _sanitize_run_mode(data.get("default_run_mode")) else: base["default_run_mode"] = _sanitize_run_mode(base.get("default_run_mode")) + + # 默认模型 + chosen_model = data.get("default_model", base.get("default_model")) + if isinstance(chosen_model, str) and chosen_model in allowed_models: + base["default_model"] = chosen_model + elif base.get("default_model") not in allowed_models: + base["default_model"] = "kimi" + return base diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index 9a0ddd2..139b38c 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -182,6 +182,51 @@ +
+
+
+
+ 默认模型 +

为新对话/登录后首次请求选择首选模型,会与默认思考模式一起生效。

+
+
+ +
+

+ Qwen-Max 仅支持快速模式;Qwen-VL 不支持深度思考模式,选择时会给出提示。 +

+
+
+
+
+
+ + {{ status }} + + + {{ error }} + +
+ +
+
+
@@ -475,6 +520,7 @@ import { ref, computed } from 'vue'; import { storeToRefs } from 'pinia'; import { usePersonalizationStore } from '@/stores/personalization'; import { useResourceStore } from '@/stores/resource'; +import { useUiStore } from '@/stores/ui'; import { useTheme } from '@/utils/theme'; import type { ThemeKey } from '@/utils/theme'; @@ -482,6 +528,7 @@ defineOptions({ name: 'PersonalizationDrawer' }); const personalization = usePersonalizationStore(); const resourceStore = useResourceStore(); +const uiStore = useUiStore(); const { visible, loading, @@ -501,12 +548,13 @@ const { const baseTabs = [ { id: 'preferences', label: '个性化设置' }, + { id: 'model', label: '模型偏好', description: '默认模型选择' }, { id: 'behavior', label: '模型行为' }, { id: 'theme', label: '主题切换', description: '浅色 / 深色 / Claude' }, { id: 'experiments', label: '实验功能', description: 'Liquid Glass' } ] as const; -type PersonalTab = 'preferences' | 'behavior' | 'theme' | 'experiments' | 'admin-monitor'; +type PersonalTab = 'preferences' | 'model' | 'behavior' | 'theme' | 'experiments' | 'admin-monitor'; const isAdmin = computed(() => (resourceStore.usageQuota.role || '').toLowerCase() === 'admin'); @@ -529,6 +577,13 @@ const runModeOptions: Array<{ id: string; label: string; desc: string; value: Ru { id: 'deep', label: '深度思考', desc: '整轮对话都使用思考模型', value: 'deep' } ]; +const modelOptions = [ + { id: 'deepseek', label: 'DeepSeek', desc: '通用 + 思考强化', value: 'deepseek' }, + { id: 'kimi', label: 'Kimi', desc: '默认模型,兼顾通用对话', value: 'kimi' }, + { id: 'qwen3-max', label: 'Qwen-Max', desc: '仅快速模式,不支持思考', value: 'qwen3-max', badge: '仅快速' }, + { id: 'qwen3-vl-plus', label: 'Qwen-VL', desc: '图文多模态,思考/快速均可', value: 'qwen3-vl-plus', badge: '图文' } +] as const; + const thinkingPresets = [ { id: 'low', label: '低', value: 10 }, { id: 'medium', label: '中', value: 5 }, @@ -583,9 +638,39 @@ const isRunModeActive = (value: RunModeValue) => { }; const setDefaultRunMode = (value: RunModeValue) => { + if (checkModeModelConflict(value, form.value.default_model)) { + return; + } personalization.setDefaultRunMode(value); }; +const setDefaultModel = (value: string) => { + if (checkModeModelConflict(form.value.default_run_mode, value)) { + return; + } + personalization.setDefaultModel(value); +}; + +const checkModeModelConflict = (mode: RunModeValue, model: string | null): boolean => { + const warnings: string[] = []; + if (model === 'qwen3-max' && mode && mode !== 'fast') { + warnings.push('Qwen-Max 仅支持快速模式,已保持原设置。'); + } + if (model === 'qwen3-vl-plus' && mode === 'deep') { + warnings.push('Qwen-VL 不支持深度思考模式,已保持原设置。'); + } + if (warnings.length) { + uiStore.pushToast({ + title: '模型/思考模式不兼容', + message: warnings.join(' '), + type: 'warning', + duration: 6000 + }); + return true; + } + return false; +}; + const handleThinkingInput = (event: Event) => { const target = event.target as HTMLInputElement; if (!target.value) { diff --git a/static/src/components/token/TokenDrawer.vue b/static/src/components/token/TokenDrawer.vue index 96af177..91d6d60 100644 --- a/static/src/components/token/TokenDrawer.vue +++ b/static/src/components/token/TokenDrawer.vue @@ -1,8 +1,8 @@