@@ -297,7 +316,8 @@ const {
toggleUpdating,
toolCategories,
thinkingIntervalDefault,
- thinkingIntervalRange
+ thinkingIntervalRange,
+ modeOptions
} = storeToRefs(personalization);
const activeTab = ref<'preferences' | 'behavior'>('preferences');
diff --git a/static/src/composables/useLegacySocket.ts b/static/src/composables/useLegacySocket.ts
index 961d756..3f793ce 100644
--- a/static/src/composables/useLegacySocket.ts
+++ b/static/src/composables/useLegacySocket.ts
@@ -529,7 +529,7 @@ export async function initializeLegacySocket(ctx: any) {
ctx.socket.on('system_ready', (data) => {
ctx.projectPath = data.project_path || '';
ctx.agentVersion = data.version || ctx.agentVersion;
- ctx.thinkingMode = !!data.thinking_mode;
+ ctx.applyThinkingModeSnapshot(data);
socketLog('系统就绪:', data);
// 系统就绪后立即加载对话列表
@@ -630,9 +630,7 @@ export async function initializeLegacySocket(ctx: any) {
if (status.conversation && status.conversation.current_id) {
ctx.currentConversationId = status.conversation.current_id;
}
- if (typeof status.thinking_mode !== 'undefined') {
- ctx.thinkingMode = !!status.thinking_mode;
- }
+ ctx.applyThinkingModeSnapshot(status);
});
// AI消息开始
diff --git a/static/src/stores/connection.ts b/static/src/stores/connection.ts
index 31a4812..1f1ff83 100644
--- a/static/src/stores/connection.ts
+++ b/static/src/stores/connection.ts
@@ -8,6 +8,7 @@ interface ConnectionState {
projectPath: string;
agentVersion: string;
thinkingMode: boolean;
+ thinkingModeOption: 'fast' | 'smart' | 'deep';
}
export const useConnectionStore = defineStore('connection', {
@@ -17,7 +18,8 @@ export const useConnectionStore = defineStore('connection', {
stopRequested: false,
projectPath: '',
agentVersion: '',
- thinkingMode: true
+ thinkingMode: false,
+ thinkingModeOption: 'fast'
}),
actions: {
setSocket(socket: Socket | null) {
@@ -43,9 +45,27 @@ export const useConnectionStore = defineStore('connection', {
},
setThinkingMode(value: boolean) {
this.thinkingMode = !!value;
+ if (!value) {
+ this.thinkingModeOption = 'fast';
+ } else if (this.thinkingModeOption === 'fast') {
+ this.thinkingModeOption = 'smart';
+ }
+ },
+ setThinkingModeOption(option: string) {
+ if (!['fast', 'smart', 'deep'].includes(option)) {
+ return;
+ }
+ this.thinkingModeOption = option as ConnectionState['thinkingModeOption'];
+ this.thinkingMode = option !== 'fast';
},
toggleThinkingMode() {
- this.thinkingMode = !this.thinkingMode;
+ if (this.thinkingModeOption === 'fast') {
+ this.setThinkingModeOption('smart');
+ } else if (this.thinkingModeOption === 'smart') {
+ this.setThinkingModeOption('deep');
+ } else {
+ this.setThinkingModeOption('fast');
+ }
}
}
});
diff --git a/static/src/stores/input.ts b/static/src/stores/input.ts
index 8de5f0e..d54f689 100644
--- a/static/src/stores/input.ts
+++ b/static/src/stores/input.ts
@@ -8,6 +8,7 @@ interface InputState {
quickMenuOpen: boolean;
toolMenuOpen: boolean;
settingsOpen: boolean;
+ modeMenuOpen: boolean;
}
export const useInputStore = defineStore('input', {
@@ -18,7 +19,8 @@ export const useInputStore = defineStore('input', {
inputIsFocused: false,
quickMenuOpen: false,
toolMenuOpen: false,
- settingsOpen: false
+ settingsOpen: false,
+ modeMenuOpen: false
}),
actions: {
setInputMessage(value: string) {
@@ -44,6 +46,7 @@ export const useInputStore = defineStore('input', {
if (!open) {
this.toolMenuOpen = false;
this.settingsOpen = false;
+ this.modeMenuOpen = false;
}
},
toggleQuickMenu() {
@@ -55,6 +58,7 @@ export const useInputStore = defineStore('input', {
this.quickMenuOpen = false;
this.toolMenuOpen = false;
this.settingsOpen = false;
+ this.modeMenuOpen = false;
},
toggleToolMenu() {
this.toolMenuOpen = !this.toolMenuOpen;
@@ -69,6 +73,13 @@ export const useInputStore = defineStore('input', {
},
setSettingsOpen(open: boolean) {
this.settingsOpen = open;
+ },
+ toggleModeMenu() {
+ this.modeMenuOpen = !this.modeMenuOpen;
+ return this.modeMenuOpen;
+ },
+ setModeMenuOpen(open: boolean) {
+ this.modeMenuOpen = open;
}
}
});
diff --git a/static/src/stores/personalization.ts b/static/src/stores/personalization.ts
index 893629a..6d1a199 100644
--- a/static/src/stores/personalization.ts
+++ b/static/src/stores/personalization.ts
@@ -9,6 +9,7 @@ interface PersonalForm {
considerations: string[];
thinking_interval: number | null;
disabled_tool_categories: string[];
+ default_mode: string;
}
interface PersonalizationState {
@@ -28,10 +29,16 @@ interface PersonalizationState {
toolCategories: Array<{ id: string; label: string }>;
thinkingIntervalDefault: number;
thinkingIntervalRange: { min: number; max: number };
+ modeOptions: Array<{ value: string; label: string; description: string }>;
}
const DEFAULT_INTERVAL = 10;
const DEFAULT_INTERVAL_RANGE = { min: 1, max: 50 };
+const DEFAULT_MODE_OPTIONS = [
+ { value: 'fast', label: '快速模式', description: '即时响应' },
+ { value: 'smart', label: '思考模式', description: '智能调度' },
+ { value: 'deep', label: '深度思考模式', description: '全程推理' }
+];
const defaultForm = (): PersonalForm => ({
enabled: false,
@@ -41,7 +48,8 @@ const defaultForm = (): PersonalForm => ({
tone: '',
considerations: [],
thinking_interval: null,
- disabled_tool_categories: []
+ disabled_tool_categories: [],
+ default_mode: 'fast'
});
export const usePersonalizationStore = defineStore('personalization', {
@@ -61,7 +69,8 @@ export const usePersonalizationStore = defineStore('personalization', {
form: defaultForm(),
toolCategories: [],
thinkingIntervalDefault: DEFAULT_INTERVAL,
- thinkingIntervalRange: { ...DEFAULT_INTERVAL_RANGE }
+ thinkingIntervalRange: { ...DEFAULT_INTERVAL_RANGE },
+ modeOptions: [...DEFAULT_MODE_OPTIONS]
}),
actions: {
async openDrawer() {
@@ -121,7 +130,10 @@ export const usePersonalizationStore = defineStore('personalization', {
tone: data.tone || '',
considerations: Array.isArray(data.considerations) ? [...data.considerations] : [],
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') : []
+ disabled_tool_categories: Array.isArray(data.disabled_tool_categories)
+ ? data.disabled_tool_categories.filter((item: unknown) => typeof item === 'string')
+ : [],
+ default_mode: typeof data.default_mode === 'string' ? data.default_mode : 'fast'
};
this.clearFeedback();
},
@@ -150,6 +162,26 @@ export const usePersonalizationStore = defineStore('personalization', {
} else {
this.toolCategories = [];
}
+ if (payload && Array.isArray(payload.thinking_mode_options) && payload.thinking_mode_options.length) {
+ this.modeOptions = payload.thinking_mode_options
+ .map((value: string) => {
+ const preset = DEFAULT_MODE_OPTIONS.find((option) => option.value === value);
+ if (preset) {
+ return preset;
+ }
+ return { value, label: value, description: '' };
+ })
+ .filter((item: { value: string }) => !!item.value);
+ } else {
+ this.modeOptions = [...DEFAULT_MODE_OPTIONS];
+ }
+ if (!this.modeOptions.some((option) => option.value === this.form.default_mode)) {
+ const fallback = this.modeOptions[0]?.value || 'fast';
+ this.form = {
+ ...this.form,
+ default_mode: fallback
+ };
+ }
},
clearFeedback() {
this.status = '';
@@ -281,6 +313,20 @@ export const usePersonalizationStore = defineStore('personalization', {
};
this.clearFeedback();
},
+ setDefaultMode(mode: string) {
+ if (!mode) {
+ return;
+ }
+ const valid = this.modeOptions.some((option) => option.value === mode);
+ if (!valid) {
+ return;
+ }
+ this.form = {
+ ...this.form,
+ default_mode: mode
+ };
+ this.clearFeedback();
+ },
updateNewConsideration(value: string) {
this.newConsideration = value;
this.clearFeedback();
diff --git a/static/src/styles/components/input/_composer.scss b/static/src/styles/components/input/_composer.scss
index b3c4fef..2180fd6 100644
--- a/static/src/styles/components/input/_composer.scss
+++ b/static/src/styles/components/input/_composer.scss
@@ -259,6 +259,11 @@
bottom: 0;
}
+.quick-submenu.mode-submenu {
+ top: auto;
+ bottom: 0;
+}
+
.menu-entry.submenu-entry {
width: 100%;
justify-content: space-between;
@@ -268,6 +273,15 @@
color: var(--claude-text-secondary);
}
+.menu-entry.submenu-entry.active {
+ background: rgba(189, 93, 58, 0.1);
+ border: 1px solid rgba(189, 93, 58, 0.3);
+}
+
+.menu-entry.submenu-entry.active .entry-arrow {
+ color: var(--claude-accent);
+}
+
.menu-entry.disabled {
opacity: 0.5;
}
diff --git a/static/src/styles/components/overlays/_overlays.scss b/static/src/styles/components/overlays/_overlays.scss
index b30666f..ca35dcd 100644
--- a/static/src/styles/components/overlays/_overlays.scss
+++ b/static/src/styles/components/overlays/_overlays.scss
@@ -170,6 +170,67 @@
margin-bottom: 18px;
}
+.mode-preference {
+ margin-bottom: 18px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.mode-preference-header {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.mode-preference-title {
+ font-weight: 600;
+ color: var(--claude-text);
+}
+
+.mode-preference-hint {
+ font-size: 13px;
+ color: var(--claude-text-secondary);
+}
+
+.mode-option-grid {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.mode-option-chip {
+ border: 1px solid rgba(118, 103, 84, 0.3);
+ border-radius: 16px;
+ padding: 10px 14px;
+ background: rgba(255, 255, 255, 0.9);
+ text-align: left;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ cursor: pointer;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
+}
+
+.mode-option-chip:hover {
+ border-color: var(--claude-accent);
+}
+
+.mode-option-chip.active {
+ border-color: var(--claude-accent);
+ background: rgba(189, 93, 58, 0.08);
+ box-shadow: 0 6px 20px rgba(189, 93, 58, 0.15);
+}
+
+.mode-option-label {
+ font-weight: 600;
+}
+
+.mode-option-desc {
+ font-size: 13px;
+ color: var(--claude-text-secondary);
+}
+
.personalization-layout {
display: grid;
grid-template-columns: 200px minmax(0, 1fr);
diff --git a/static/src/styles/components/panels/_left-panel.scss b/static/src/styles/components/panels/_left-panel.scss
index 8c62142..67b0d91 100644
--- a/static/src/styles/components/panels/_left-panel.scss
+++ b/static/src/styles/components/panels/_left-panel.scss
@@ -137,6 +137,16 @@
box-shadow: 0 8px 20px rgba(255, 204, 77, 0.35);
}
+.mode-indicator.smart {
+ background: var(--claude-accent);
+ box-shadow: 0 8px 20px rgba(189, 93, 58, 0.25);
+}
+
+.mode-indicator.deep {
+ background: linear-gradient(135deg, #7c5dfa, #4b32c3);
+ box-shadow: 0 8px 20px rgba(76, 50, 195, 0.35);
+}
+
.mode-indicator .icon {
--icon-size: 18px;
color: inherit;
diff --git a/utils/api_client.py b/utils/api_client.py
index e90b6ac..f8ff8c5 100644
--- a/utils/api_client.py
+++ b/utils/api_client.py
@@ -46,6 +46,7 @@ class DeepSeekClient:
"model_id": THINKING_MODEL_ID or MODEL_ID
}
self.thinking_mode = thinking_mode # True=智能思考模式, False=快速模式
+ self.deep_thinking_mode = False
self.web_mode = web_mode # Web模式标志,用于禁用print输出
# 兼容旧代码路径
self.api_base_url = self.fast_api_config["base_url"]
@@ -57,6 +58,7 @@ class DeepSeekClient:
self.force_thinking_next_call = False # 单次强制思考
self.skip_thinking_next_call = False # 单次强制快速
self.last_call_used_thinking = False # 最近一次调用是否使用思考模型
+ self.deep_thinking_mode = False
def _print(self, message: str, end: str = "\n", flush: bool = False):
"""安全的打印函数,在Web模式下不输出"""
@@ -154,6 +156,8 @@ class DeepSeekClient:
def get_current_thinking_mode(self) -> bool:
"""获取当前应该使用的思考模式"""
+ if self.deep_thinking_mode:
+ return True
if not self.thinking_mode:
return False
if self.force_thinking_next_call:
diff --git a/web_server.py b/web_server.py
index 53951c0..a3d88c7 100644
--- a/web_server.py
+++ b/web_server.py
@@ -57,6 +57,7 @@ from modules.personalization_manager import (
save_personalization_config,
THINKING_INTERVAL_MIN,
THINKING_INTERVAL_MAX,
+ THINKING_MODE_OPTIONS,
)
from modules.user_container_manager import UserContainerManager
from modules.usage_tracker import UsageTracker
@@ -484,6 +485,13 @@ def with_terminal(func):
return jsonify({"error": str(exc), "code": "resource_busy"}), 503
if not terminal or not workspace:
return jsonify({"error": "System not initialized"}), 503
+ preferred_mode = session.get('thinking_mode_option')
+ if not preferred_mode:
+ preferred_mode = getattr(terminal, "default_thinking_mode_option", "fast")
+ session['thinking_mode_option'] = preferred_mode
+ if preferred_mode != getattr(terminal, "thinking_mode_option", None):
+ terminal.set_thinking_mode_option(preferred_mode)
+ session['thinking_mode'] = terminal.thinking_mode
kwargs.update({
'terminal': terminal,
'workspace': workspace,
@@ -661,6 +669,11 @@ def apply_thinking_schedule(terminal: WebTerminal):
client.skip_thinking_next_call = False
return
state = get_thinking_state(terminal)
+ if getattr(terminal, "deep_thinking_mode", False):
+ client.force_thinking_next_call = False
+ client.skip_thinking_next_call = False
+ state["fast_streak"] = 0
+ return
awaiting_writes = getattr(terminal, "pending_append_request", None) or getattr(terminal, "pending_modify_request", None)
if awaiting_writes:
client.skip_thinking_next_call = True
@@ -699,6 +712,9 @@ def update_thinking_after_call(terminal: WebTerminal):
if not getattr(terminal, "thinking_mode", False):
return
state = get_thinking_state(terminal)
+ if getattr(terminal, "deep_thinking_mode", False):
+ state["fast_streak"] = 0
+ return
if terminal.api_client.last_call_used_thinking:
state["fast_streak"] = 0
else:
@@ -831,7 +847,9 @@ def login():
session['logged_in'] = True
session['username'] = record.username
- session['thinking_mode'] = app.config.get('DEFAULT_THINKING_MODE', False)
+ default_mode_option = app.config.get('DEFAULT_THINKING_MODE_OPTION', 'fast')
+ session['thinking_mode_option'] = default_mode_option if default_mode_option in THINKING_MODE_OPTIONS else 'fast'
+ session['thinking_mode'] = session['thinking_mode_option'] != 'fast'
session.permanent = True
clear_failures("login", identifier=client_ip)
workspace = user_manager.ensure_user_workspace(record.username)
@@ -979,6 +997,8 @@ def get_status(terminal: WebTerminal, workspace: UserWorkspace, username: str):
print(f"[Status] 获取当前对话信息失败: {e}")
status['project_path'] = str(workspace.project_path)
+ status['thinking_mode_option'] = getattr(terminal, "thinking_mode_option", "fast")
+ status['deep_thinking_mode'] = getattr(terminal, "deep_thinking_mode", False)
try:
status['container'] = container_manager.get_container_status(username)
except Exception as exc:
@@ -1041,11 +1061,16 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
"""切换思考模式"""
try:
data = request.get_json() or {}
- desired_mode = bool(data.get('thinking_mode'))
- terminal.thinking_mode = desired_mode
- terminal.api_client.thinking_mode = desired_mode
+ mode_option = data.get('mode')
+ if isinstance(mode_option, str) and mode_option not in THINKING_MODE_OPTIONS:
+ mode_option = None
+ if not mode_option:
+ desired_mode = bool(data.get('thinking_mode'))
+ mode_option = "smart" if desired_mode else "fast"
+ terminal.set_thinking_mode_option(mode_option)
terminal.api_client.start_new_task()
- session['thinking_mode'] = desired_mode
+ session['thinking_mode_option'] = mode_option
+ session['thinking_mode'] = terminal.thinking_mode
# 更新当前对话的元数据
ctx = terminal.context_manager
if ctx.current_conversation_id:
@@ -1055,17 +1080,21 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
messages=ctx.conversation_history,
project_path=str(ctx.project_path),
todo_list=ctx.todo_list,
- thinking_mode=desired_mode
+ thinking_mode=terminal.thinking_mode
)
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": status.get("thinking_mode")
+ "data": {
+ "thinking_mode": terminal.thinking_mode,
+ "mode": terminal.thinking_mode_option,
+ "deep": terminal.deep_thinking_mode
+ }
})
except Exception as exc:
print(f"[API] 切换思考模式失败: {exc}")
@@ -1091,7 +1120,8 @@ def get_personalization_settings(terminal: WebTerminal, workspace: UserWorkspace
"thinking_interval_range": {
"min": THINKING_INTERVAL_MIN,
"max": THINKING_INTERVAL_MAX
- }
+ },
+ "thinking_mode_options": THINKING_MODE_OPTIONS
})
except Exception as exc:
return jsonify({"success": False, "error": str(exc)}), 500
@@ -1108,6 +1138,8 @@ def update_personalization_settings(terminal: WebTerminal, workspace: UserWorksp
config = save_personalization_config(workspace.data_dir, payload)
try:
terminal.apply_personalization_preferences(config)
+ session['thinking_mode_option'] = getattr(terminal, "thinking_mode_option", session.get('thinking_mode_option', 'fast'))
+ session['thinking_mode'] = terminal.thinking_mode
except Exception as exc:
debug_log(f"应用个性化偏好失败: {exc}")
return jsonify({
@@ -1118,7 +1150,8 @@ def update_personalization_settings(terminal: WebTerminal, workspace: UserWorksp
"thinking_interval_range": {
"min": THINKING_INTERVAL_MIN,
"max": THINKING_INTERVAL_MAX
- }
+ },
+ "thinking_mode_options": THINKING_MODE_OPTIONS
})
except ValueError as exc:
return jsonify({"success": False, "error": str(exc)}), 400
@@ -3791,14 +3824,17 @@ async def handle_task_with_sender(terminal: WebTerminal, message, sender, client
"content": assistant_content,
"tool_calls": tool_calls
}
-
+ reasoning_payload = current_thinking if getattr(web_terminal, "deep_thinking_mode", False) else None
+ if reasoning_payload:
+ assistant_message["reasoning_content"] = reasoning_payload
+
messages.append(assistant_message)
- if assistant_content or current_thinking or tool_calls:
+ if assistant_content or reasoning_payload or tool_calls:
web_terminal.context_manager.add_conversation(
"assistant",
assistant_content,
tool_calls=tool_calls if tool_calls else None,
- reasoning_content=current_thinking or None
+ reasoning_content=reasoning_payload
)
# 为下一轮迭代重置流状态标志,但保留 full_response 供上面保存使用
@@ -4312,7 +4348,9 @@ def initialize_system(path: str, thinking_mode: bool = False):
print(f"[Init] 自动修复: {'开启' if AUTO_FIX_TOOL_CALL else '关闭'}")
print(f"[Init] 调试日志: {DEBUG_LOG_FILE}")
+ default_option = "smart" if thinking_mode else "fast"
app.config['DEFAULT_THINKING_MODE'] = thinking_mode
+ app.config['DEFAULT_THINKING_MODE_OPTION'] = default_option
print(f"{OUTPUT_FORMATS['success']} Web系统初始化完成(多用户模式)")
def run_server(path: str, thinking_mode: bool = False, port: int = DEFAULT_PORT, debug: bool = False):