feat: add deep thinking mode

This commit is contained in:
JOJO 2025-11-30 13:39:50 +08:00
parent 02ab023ad7
commit 2f4ea590a8
17 changed files with 480 additions and 53 deletions

View File

@ -53,6 +53,7 @@ from modules.easter_egg_manager import EasterEggManager
from modules.personalization_manager import (
load_personalization_config,
build_personalization_prompt,
THINKING_MODE_OPTIONS,
)
try:
from config.limits import THINKING_FAST_INTERVAL
@ -86,6 +87,11 @@ class MainTerminal:
# 初始化组件
self.api_client = DeepSeekClient(thinking_mode=thinking_mode)
self.thinking_mode_option = "smart" if thinking_mode else "fast"
self.default_thinking_mode_option = self.thinking_mode_option
self.deep_thinking_mode = self.thinking_mode_option == "deep"
self.api_client.deep_thinking_mode = self.deep_thinking_mode
self.set_thinking_mode_option(self.thinking_mode_option)
self.context_manager = ContextManager(project_path, data_dir=str(self.data_dir))
self.context_manager.main_terminal = self
self.container_mount_path = TERMINAL_SANDBOX_MOUNT_PATH or "/workspace"
@ -205,6 +211,17 @@ class MainTerminal:
except Exception:
pass
def set_thinking_mode_option(self, option: str):
previous_option = getattr(self, "thinking_mode_option", "fast")
normalized = option if option in THINKING_MODE_OPTIONS else "fast"
self.thinking_mode_option = normalized
self.thinking_mode = normalized in ("smart", "deep")
self.deep_thinking_mode = normalized == "deep"
self.api_client.thinking_mode = self.thinking_mode
self.api_client.deep_thinking_mode = self.deep_thinking_mode
if previous_option != normalized:
self.api_client.start_new_task()
def update_container_session(self, session: Optional["ContainerHandle"]):
self._apply_container_session(session)
if getattr(self, "terminal_manager", None):
@ -352,6 +369,10 @@ class MainTerminal:
for key, category in TOOL_CATEGORIES.items():
self.tool_category_states[key] = False if key in disabled_categories else category.default_enabled
self._refresh_disabled_tools()
default_mode = effective_config.get("default_mode")
if isinstance(default_mode, str) and default_mode in THINKING_MODE_OPTIONS:
self.default_thinking_mode_option = default_mode
self.set_thinking_mode_option(default_mode)
def _handle_read_tool(self, arguments: Dict) -> Dict:
@ -700,7 +721,7 @@ class MainTerminal:
assistant_content_parts = []
# 添加思考内容
if final_thinking:
if final_thinking and not self.deep_thinking_mode:
assistant_content_parts.append(f"<think>\n{final_thinking}\n</think>")
# 添加回复内容
@ -714,7 +735,8 @@ class MainTerminal:
self.context_manager.add_conversation(
"assistant",
assistant_content,
collected_tool_calls if collected_tool_calls else None
collected_tool_calls if collected_tool_calls else None,
reasoning_content=final_thinking if self.deep_thinking_mode else None
)
# 3. 保存独立的tool消息
@ -2360,6 +2382,8 @@ class MainTerminal:
tool_calls = conv.get("tool_calls") or []
if tool_calls and self._tool_calls_followed_by_tools(conversation, idx, tool_calls):
message["tool_calls"] = tool_calls
if conv.get("reasoning_content") and self.thinking_mode_option == "deep":
message["reasoning_content"] = conv["reasoning_content"]
messages.append(message)
elif conv["role"] == "tool":

View File

@ -21,6 +21,7 @@ MAX_CONSIDERATION_ITEMS = 10
TONE_PRESETS = ["健谈", "幽默", "直言不讳", "鼓励性", "诗意", "企业商务", "打破常规", "同理心"]
THINKING_INTERVAL_MIN = 1
THINKING_INTERVAL_MAX = 50
THINKING_MODE_OPTIONS = ("fast", "smart", "deep")
DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
"enabled": False,
@ -31,6 +32,7 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
"considerations": [],
"thinking_interval": None,
"disabled_tool_categories": [],
"default_mode": "fast",
}
__all__ = [
@ -38,6 +40,9 @@ __all__ = [
"DEFAULT_PERSONALIZATION_CONFIG",
"TONE_PRESETS",
"MAX_CONSIDERATION_ITEMS",
"THINKING_INTERVAL_MIN",
"THINKING_INTERVAL_MAX",
"THINKING_MODE_OPTIONS",
"load_personalization_config",
"save_personalization_config",
"ensure_personalization_config",
@ -123,6 +128,10 @@ def sanitize_personalization_payload(
base["disabled_tool_categories"] = _sanitize_tool_categories(data.get("disabled_tool_categories"), allowed_tool_categories)
else:
base["disabled_tool_categories"] = _sanitize_tool_categories(base.get("disabled_tool_categories"), allowed_tool_categories)
if "default_mode" in data:
base["default_mode"] = _sanitize_default_mode(data.get("default_mode"))
else:
base["default_mode"] = _sanitize_default_mode(base.get("default_mode"))
return base
@ -222,3 +231,11 @@ def _sanitize_tool_categories(value: Any, allowed: set) -> list:
if candidate not in result:
result.append(candidate)
return result
def _sanitize_default_mode(value: Any) -> str:
if isinstance(value, str):
normalized = value.strip()
if normalized in THINKING_MODE_OPTIONS:
return normalized
return DEFAULT_PERSONALIZATION_CONFIG["default_mode"]

View File

@ -63,6 +63,7 @@
:icon-style="iconStyle"
:agent-version="agentVersion"
:thinking-mode="thinkingMode"
:thinking-mode-option="thinkingModeOption"
:is-connected="isConnected"
:panel-menu-open="panelMenuOpen"
:panel-mode="panelMode"
@ -147,12 +148,14 @@
:is-connected="isConnected"
:streaming-message="streamingMessage"
:uploading="uploading"
:thinking-mode="thinkingMode"
:quick-menu-open="quickMenuOpen"
:tool-menu-open="toolMenuOpen"
:tool-settings="toolSettings"
:tool-settings-loading="toolSettingsLoading"
:settings-open="settingsOpen"
:mode-menu-open="modeMenuOpen"
:thinking-mode-option="thinkingModeOption"
:mode-options="modeOptions"
:compressing="compressing"
:current-conversation-id="currentConversationId"
:icon-style="iconStyle"
@ -166,7 +169,8 @@
@send-or-stop="handleSendOrStop"
@quick-upload="handleQuickUpload"
@toggle-tool-menu="toggleToolMenu"
@quick-mode-toggle="handleQuickModeToggle"
@toggle-mode-menu="toggleModeMenu"
@select-mode="selectThinkingMode"
@toggle-settings="toggleSettings"
@update-tool-category="updateToolCategory"
@realtime-terminal="handleRealtimeTerminalClick"
@ -280,6 +284,7 @@
:icon-style="iconStyle"
:agent-version="agentVersion"
:thinking-mode="thinkingMode"
:thinking-mode-option="thinkingModeOption"
:is-connected="isConnected"
:panel-menu-open="panelMenuOpen"
:panel-mode="panelMode"

View File

@ -97,6 +97,11 @@ if (window.visualViewport) {
}
const ENABLE_APP_DEBUG_LOGS = false;
const MODE_OPTIONS = [
{ value: 'fast', label: '快速模式', description: '即时响应', icon: 'zap' },
{ value: 'smart', label: '思考模式', description: '智能调度', icon: 'brain' },
{ value: 'deep', label: '深度思考模式', description: '全程推理', icon: 'brain' }
];
function debugLog(...args) {
if (!ENABLE_APP_DEBUG_LOGS) {
return;
@ -135,7 +140,8 @@ const appOptions = {
// 工具控制菜单
icons: ICONS,
toolCategoryIcons: TOOL_CATEGORY_ICON_MAP
toolCategoryIcons: TOOL_CATEGORY_ICON_MAP,
modeOptions: MODE_OPTIONS
}
},
@ -199,7 +205,8 @@ const appOptions = {
'stopRequested',
'projectPath',
'agentVersion',
'thinkingMode'
'thinkingMode',
'thinkingModeOption'
]),
...mapState(useFileStore, ['contextMenu', 'fileTree', 'expandedFolders', 'todoList']),
...mapWritableState(useUiStore, [
@ -250,7 +257,8 @@ const appOptions = {
'inputIsFocused',
'quickMenuOpen',
'toolMenuOpen',
'settingsOpen'
'settingsOpen',
'modeMenuOpen'
]),
...mapWritableState(useToolStore, [
'preparingTools',
@ -483,6 +491,8 @@ const appOptions = {
inputSetToolMenuOpen: 'setToolMenuOpen',
inputToggleSettingsMenu: 'toggleSettingsMenu',
inputSetSettingsOpen: 'setSettingsOpen',
inputToggleModeMenu: 'toggleModeMenu',
inputSetModeMenuOpen: 'setModeMenuOpen',
inputSetMessage: 'setInputMessage',
inputClearMessage: 'clearInputMessage',
inputSetLineCount: 'setInputLineCount',
@ -1042,7 +1052,7 @@ const appOptions = {
const statusData = await statusResponse.json();
this.projectPath = statusData.project_path || '';
this.agentVersion = statusData.version || this.agentVersion;
this.thinkingMode = !!statusData.thinking_mode;
this.applyThinkingModeSnapshot(statusData);
this.applyStatusSnapshot(statusData);
await this.fetchUsageQuota();
@ -1817,20 +1827,28 @@ const appOptions = {
return this.subAgentFetch();
},
async toggleThinkingMode() {
const nextMode = !this.thinkingMode;
async setThinkingModeOption(option) {
if (!this.isConnected || this.streamingMessage) {
return;
}
if (!this.modeOptions.some((item) => item.value === option)) {
return;
}
try {
const response = await fetch('/api/thinking-mode', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ thinking_mode: nextMode })
body: JSON.stringify({ mode: option })
});
const data = await response.json();
if (response.ok && data.success) {
const actual = typeof data.data === 'boolean' ? data.data : nextMode;
this.thinkingMode = actual;
const payload = data.data || {};
this.applyThinkingModeSnapshot({
thinking_mode_option: payload.mode || option,
thinking_mode: typeof payload.thinking_mode === 'boolean' ? payload.thinking_mode : undefined
});
return;
}
throw new Error(data.message || data.error || '切换失败');
@ -1838,12 +1856,40 @@ const appOptions = {
console.error('切换思考模式失败:', error);
this.uiPushToast({
title: '切换思考模式失败',
message: error.message || '请稍后重试',
message: error?.message || '请稍后重试',
type: 'error'
});
}
},
applyThinkingModeSnapshot(payload) {
const normalized = this.normalizeModeOption(
payload && payload.thinking_mode_option,
Object.prototype.hasOwnProperty.call(payload || {}, 'thinking_mode') ? payload!.thinking_mode : undefined,
Object.prototype.hasOwnProperty.call(payload || {}, 'deep_thinking_mode') ? payload!.deep_thinking_mode : undefined
);
this.thinkingModeOption = normalized;
this.thinkingMode = normalized !== 'fast';
},
normalizeModeOption(option, fallback, deepFlag) {
if (typeof deepFlag === 'boolean') {
if (deepFlag) {
return 'deep';
}
if (!deepFlag && option === 'deep') {
return fallback ? 'smart' : 'fast';
}
}
if (typeof option === 'string' && this.modeOptions.some((item) => item.value === option)) {
return option;
}
if (typeof fallback === 'boolean') {
return fallback ? 'smart' : 'fast';
}
return 'fast';
},
triggerFileUpload() {
if (this.uploading) {
return;
@ -1994,6 +2040,7 @@ const appOptions = {
const nextState = this.inputToggleToolMenu();
if (nextState) {
this.inputSetSettingsOpen(false);
this.inputSetModeMenuOpen(false);
if (!this.quickMenuOpen) {
this.inputOpenQuickMenu();
}
@ -2003,6 +2050,28 @@ const appOptions = {
}
},
toggleModeMenu() {
if (!this.isConnected) {
return;
}
const nextState = this.inputToggleModeMenu();
if (nextState) {
this.inputSetToolMenuOpen(false);
this.inputSetSettingsOpen(false);
if (!this.quickMenuOpen) {
this.inputOpenQuickMenu();
}
}
},
selectThinkingMode(option) {
if (!option) {
return;
}
this.setThinkingModeOption(option);
this.inputSetModeMenuOpen(false);
},
toggleQuickMenu() {
if (!this.isConnected) {
return;
@ -2025,7 +2094,20 @@ const appOptions = {
if (!this.isConnected || this.streamingMessage) {
return;
}
this.toggleThinkingMode();
const next = this.nextModeOption();
this.setThinkingModeOption(next);
},
nextModeOption() {
const order = this.modeOptions.map((item) => item.value);
if (!order.length) {
return 'fast';
}
const currentIndex = order.indexOf(this.thinkingModeOption);
if (currentIndex === -1) {
return order[0];
}
return order[(currentIndex + 1) % order.length] || order[0];
},
handleInputChange() {
@ -2217,6 +2299,7 @@ const appOptions = {
const nextState = this.inputToggleSettingsMenu();
if (nextState) {
this.inputSetToolMenuOpen(false);
this.inputSetModeMenuOpen(false);
if (!this.quickMenuOpen) {
this.inputOpenQuickMenu();
}

View File

@ -41,18 +41,21 @@
:is-connected="isConnected"
:uploading="uploading"
:streaming-message="streamingMessage"
:thinking-mode="thinkingMode"
:tool-menu-open="toolMenuOpen"
:tool-settings="toolSettings"
:tool-settings-loading="toolSettingsLoading"
:settings-open="settingsOpen"
:mode-menu-open="modeMenuOpen"
:thinking-mode-option="thinkingModeOption"
:mode-options="modeOptions"
:compressing="compressing"
:current-conversation-id="currentConversationId"
:icon-style="iconStyle"
:tool-category-icon="toolCategoryIcon"
@quick-upload="triggerQuickUpload"
@toggle-tool-menu="$emit('toggle-tool-menu')"
@quick-mode-toggle="$emit('quick-mode-toggle')"
@toggle-mode-menu="$emit('toggle-mode-menu')"
@select-mode="value => $emit('select-mode', value)"
@toggle-settings="$emit('toggle-settings')"
@update-tool-category="(id, enabled) => $emit('update-tool-category', id, enabled)"
@realtime-terminal="$emit('realtime-terminal')"
@ -81,7 +84,8 @@ const emit = defineEmits([
'send-or-stop',
'quick-upload',
'toggle-tool-menu',
'quick-mode-toggle',
'toggle-mode-menu',
'select-mode',
'toggle-settings',
'update-tool-category',
'realtime-terminal',
@ -98,16 +102,18 @@ const props = defineProps<{
isConnected: boolean;
streamingMessage: boolean;
uploading: boolean;
thinkingMode: boolean;
quickMenuOpen: boolean;
toolMenuOpen: boolean;
toolSettings: Array<{ id: string; label: string; enabled: boolean }>;
toolSettingsLoading: boolean;
settingsOpen: boolean;
modeMenuOpen: boolean;
compressing: boolean;
currentConversationId: string | null;
iconStyle: (key: string) => Record<string, string>;
toolCategoryIcon: (categoryId: string) => string;
thinkingModeOption: string;
modeOptions: Array<{ value: string; label: string; description: string; icon: string }>;
}>();
const inputStore = useInputStore();

View File

@ -15,11 +15,14 @@
</button>
<button
type="button"
class="menu-entry"
@click="$emit('quick-mode-toggle')"
class="menu-entry has-submenu"
@click.stop="$emit('toggle-mode-menu')"
:disabled="streamingMessage || !isConnected"
>
{{ thinkingMode ? '快速模式' : '思考模式' }}
<span class="submenu-label">
<span>{{ modeLabel }}</span>
</span>
<span class="entry-arrow"></span>
</button>
<button
type="button"
@ -93,11 +96,35 @@
</div>
</div>
</transition>
<transition name="submenu-slide">
<div class="quick-submenu mode-submenu" v-if="modeMenuOpen">
<div class="submenu-list">
<button
v-for="mode in resolvedModeOptions"
:key="mode.value"
type="button"
class="menu-entry submenu-entry"
:class="{ active: thinkingModeOption === mode.value }"
@click.stop="$emit('select-mode', mode.value)"
:disabled="streamingMessage || !isConnected"
>
<span class="submenu-label">
<span class="icon icon-sm" :style="getIconStyle(mode.icon)" aria-hidden="true"></span>
<span>{{ mode.label }}</span>
</span>
<span class="entry-arrow">{{ mode.description }}</span>
</button>
</div>
</div>
</transition>
</div>
</transition>
</template>
<script setup lang="ts">
import { computed } from 'vue';
defineOptions({ name: 'QuickMenu' });
const props = defineProps<{
@ -105,22 +132,25 @@ const props = defineProps<{
isConnected: boolean;
uploading: boolean;
streamingMessage: boolean;
thinkingMode: boolean;
thinkingModeOption: string;
toolMenuOpen: boolean;
toolSettings: Array<{ id: string; label: string; enabled: boolean }>;
toolSettingsLoading: boolean;
settingsOpen: boolean;
modeMenuOpen: boolean;
compressing: boolean;
currentConversationId: string | null;
iconStyle?: (key: string) => Record<string, string>;
toolCategoryIcon: (categoryId: string) => string;
modeOptions: Array<{ value: string; label: string; description: string; icon: string }>;
}>();
defineEmits<{
(event: 'quick-upload'): void;
(event: 'toggle-tool-menu'): void;
(event: 'quick-mode-toggle'): void;
(event: 'toggle-settings'): void;
(event: 'toggle-mode-menu'): void;
(event: 'select-mode', option: string): void;
(event: 'update-tool-category', id: string, enabled: boolean): void;
(event: 'realtime-terminal'): void;
(event: 'toggle-focus-panel'): void;
@ -129,4 +159,23 @@ defineEmits<{
}>();
const getIconStyle = (key: string) => (props.iconStyle ? props.iconStyle(key) : {});
const DEFAULT_MODE_OPTIONS = [
{ value: 'fast', label: '快速模式', description: '即时响应', icon: 'zap' },
{ value: 'smart', label: '思考模式', description: '智能调度', icon: 'brain' },
{ value: 'deep', label: '深度思考模式', description: '全程推理', icon: 'brain' }
];
const resolvedModeOptions = computed(() => {
const source =
Array.isArray(props.modeOptions) && props.modeOptions.length ? props.modeOptions : DEFAULT_MODE_OPTIONS;
return source.map((item) => ({
value: item.value,
label: item.label || item.value,
description: item.description || '',
icon: item.icon || (item.value === 'fast' ? 'zap' : 'brain')
}));
});
const modeLabel = computed(() => {
const found = resolvedModeOptions.value.find((option) => option.value === props.thinkingModeOption);
return found ? found.label : '快速模式';
});
</script>

View File

@ -14,15 +14,15 @@
<button
type="button"
class="mode-indicator"
:class="{ thinking: thinkingMode, fast: !thinkingMode }"
:title="thinkingMode ? '思考模式(点击切换)' : '快速模式(点击切换)'"
:class="modeIndicatorClass"
:title="modeTitle"
@click="$emit('toggle-thinking-mode')"
>
<transition name="mode-icon" mode="out-in">
<span
class="icon icon-sm"
:style="iconStyle(thinkingMode ? 'brain' : 'zap')"
:key="thinkingMode ? 'brain' : 'zap'"
:style="iconStyle(modeIconKey)"
:key="modeIconKey"
aria-hidden="true"
></span>
</transition>
@ -142,6 +142,7 @@ const props = defineProps<{
iconStyle: (key: string) => Record<string, string>;
agentVersion: string | null;
thinkingMode: boolean;
thinkingModeOption: 'fast' | 'smart' | 'deep';
isConnected: boolean;
panelMenuOpen: boolean;
panelMode: 'files' | 'todo' | 'subAgents';
@ -155,6 +156,26 @@ defineEmits<{
}>();
const panelMenuWrapper = ref<HTMLElement | null>(null);
const MODE_LABELS: Record<string, string> = {
fast: '快速模式(点击切换)',
smart: '思考模式(点击切换)',
deep: '深度思考模式(点击切换)'
};
const MODE_ICONS: Record<string, string> = {
fast: 'zap',
smart: 'brain',
deep: 'brain'
};
const modeIndicatorClass = computed(() => {
const option = props.thinkingModeOption || (props.thinkingMode ? 'smart' : 'fast');
return {
fast: option === 'fast',
smart: option === 'smart',
deep: option === 'deep'
};
});
const modeTitle = computed(() => MODE_LABELS[props.thinkingModeOption] || MODE_LABELS[props.thinkingMode ? 'smart' : 'fast']);
const modeIconKey = computed(() => MODE_ICONS[props.thinkingModeOption] || MODE_ICONS[props.thinkingMode ? 'smart' : 'fast']);
const panelStyle = computed(() => {
if (props.collapsed) {
return {

View File

@ -69,6 +69,25 @@
</span>
</label>
</div>
<div class="mode-preference">
<div class="mode-preference-header">
<span class="mode-preference-title">默认运行模式</span>
<span class="mode-preference-hint">用于新任务的默认推理方式</span>
</div>
<div class="mode-option-grid">
<button
v-for="option in modeOptions"
:key="option.value"
type="button"
class="mode-option-chip"
:class="{ active: form.default_mode === option.value }"
@click.prevent="personalization.setDefaultMode(option.value)"
>
<span class="mode-option-label">{{ option.label }}</span>
<span class="mode-option-desc">{{ option.description }}</span>
</button>
</div>
</div>
<div class="personalization-sections">
<div class="personal-left-column">
<div class="personal-section personal-info">
@ -297,7 +316,8 @@ const {
toggleUpdating,
toolCategories,
thinkingIntervalDefault,
thinkingIntervalRange
thinkingIntervalRange,
modeOptions
} = storeToRefs(personalization);
const activeTab = ref<'preferences' | 'behavior'>('preferences');

View File

@ -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消息开始

View File

@ -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');
}
}
}
});

View File

@ -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;
}
}
});

View File

@ -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();

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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:

View File

@ -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,7 +1080,7 @@ 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}")
@ -1065,7 +1090,11 @@ def update_thinking_mode(terminal: WebTerminal, workspace: UserWorkspace, userna
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):