feat: persist behavior preferences
This commit is contained in:
parent
2847ade631
commit
02ab023ad7
@ -4,7 +4,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Set, TYPE_CHECKING
|
from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -54,6 +54,10 @@ from modules.personalization_manager import (
|
|||||||
load_personalization_config,
|
load_personalization_config,
|
||||||
build_personalization_prompt,
|
build_personalization_prompt,
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
from config.limits import THINKING_FAST_INTERVAL
|
||||||
|
except ImportError:
|
||||||
|
THINKING_FAST_INTERVAL = 10
|
||||||
from modules.container_monitor import collect_stats, inspect_state
|
from modules.container_monitor import collect_stats, inspect_state
|
||||||
from core.tool_config import TOOL_CATEGORIES
|
from core.tool_config import TOOL_CATEGORIES
|
||||||
from utils.api_client import DeepSeekClient
|
from utils.api_client import DeepSeekClient
|
||||||
@ -133,6 +137,9 @@ class MainTerminal:
|
|||||||
self.disabled_tools = set()
|
self.disabled_tools = set()
|
||||||
self.disabled_notice_tools = set()
|
self.disabled_notice_tools = set()
|
||||||
self._refresh_disabled_tools()
|
self._refresh_disabled_tools()
|
||||||
|
self.thinking_fast_interval = THINKING_FAST_INTERVAL
|
||||||
|
self.default_disabled_tool_categories: List[str] = []
|
||||||
|
self.apply_personalization_preferences()
|
||||||
|
|
||||||
# 新增:自动开始新对话
|
# 新增:自动开始新对话
|
||||||
self._ensure_conversation()
|
self._ensure_conversation()
|
||||||
@ -319,6 +326,33 @@ class MainTerminal:
|
|||||||
self.context_manager.add_conversation("system", message, metadata=metadata)
|
self.context_manager.add_conversation("system", message, metadata=metadata)
|
||||||
print(f"{OUTPUT_FORMATS['info']} {message}")
|
print(f"{OUTPUT_FORMATS['info']} {message}")
|
||||||
|
|
||||||
|
def apply_personalization_preferences(self, config: Optional[Dict[str, Any]] = None):
|
||||||
|
"""Apply persisted personalization settings that affect runtime behavior."""
|
||||||
|
try:
|
||||||
|
effective_config = config or load_personalization_config(self.data_dir)
|
||||||
|
except Exception:
|
||||||
|
effective_config = {}
|
||||||
|
|
||||||
|
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):
|
||||||
|
disabled_categories = [
|
||||||
|
key for key in raw_disabled
|
||||||
|
if isinstance(key, str) and key in TOOL_CATEGORIES
|
||||||
|
]
|
||||||
|
self.default_disabled_tool_categories = disabled_categories
|
||||||
|
|
||||||
|
# Reset category states to defaults before applying overrides
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
def _handle_read_tool(self, arguments: Dict) -> Dict:
|
def _handle_read_tool(self, arguments: Dict) -> Dict:
|
||||||
"""集中处理 read_file 工具的三种模式。"""
|
"""集中处理 read_file 工具的三种模式。"""
|
||||||
|
|||||||
@ -7,11 +7,20 @@ from copy import deepcopy
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Iterable, Optional, Union
|
from typing import Any, Dict, Iterable, Optional, Union
|
||||||
|
|
||||||
|
try:
|
||||||
|
from config.limits import THINKING_FAST_INTERVAL
|
||||||
|
except ImportError:
|
||||||
|
THINKING_FAST_INTERVAL = 10
|
||||||
|
|
||||||
|
from core.tool_config import TOOL_CATEGORIES
|
||||||
|
|
||||||
PERSONALIZATION_FILENAME = "personalization.json"
|
PERSONALIZATION_FILENAME = "personalization.json"
|
||||||
MAX_SHORT_FIELD_LENGTH = 20
|
MAX_SHORT_FIELD_LENGTH = 20
|
||||||
MAX_CONSIDERATION_LENGTH = 50
|
MAX_CONSIDERATION_LENGTH = 50
|
||||||
MAX_CONSIDERATION_ITEMS = 10
|
MAX_CONSIDERATION_ITEMS = 10
|
||||||
TONE_PRESETS = ["健谈", "幽默", "直言不讳", "鼓励性", "诗意", "企业商务", "打破常规", "同理心"]
|
TONE_PRESETS = ["健谈", "幽默", "直言不讳", "鼓励性", "诗意", "企业商务", "打破常规", "同理心"]
|
||||||
|
THINKING_INTERVAL_MIN = 1
|
||||||
|
THINKING_INTERVAL_MAX = 50
|
||||||
|
|
||||||
DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
|
DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
|
||||||
"enabled": False,
|
"enabled": False,
|
||||||
@ -20,6 +29,8 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
|
|||||||
"profession": "",
|
"profession": "",
|
||||||
"tone": "",
|
"tone": "",
|
||||||
"considerations": [],
|
"considerations": [],
|
||||||
|
"thinking_interval": None,
|
||||||
|
"disabled_tool_categories": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -86,6 +97,7 @@ def sanitize_personalization_payload(
|
|||||||
if fallback:
|
if fallback:
|
||||||
base.update(fallback)
|
base.update(fallback)
|
||||||
data = payload or {}
|
data = payload or {}
|
||||||
|
allowed_tool_categories = set(TOOL_CATEGORIES.keys())
|
||||||
|
|
||||||
def _resolve_short_field(key: str) -> str:
|
def _resolve_short_field(key: str) -> str:
|
||||||
if key in data:
|
if key in data:
|
||||||
@ -101,6 +113,16 @@ def sanitize_personalization_payload(
|
|||||||
base["considerations"] = _sanitize_considerations(data.get("considerations"))
|
base["considerations"] = _sanitize_considerations(data.get("considerations"))
|
||||||
else:
|
else:
|
||||||
base["considerations"] = _sanitize_considerations(base.get("considerations", []))
|
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 "disabled_tool_categories" in data:
|
||||||
|
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)
|
||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
@ -172,3 +194,31 @@ def _sanitize_considerations(value: Any) -> list:
|
|||||||
if len(cleaned) >= MAX_CONSIDERATION_ITEMS:
|
if len(cleaned) >= MAX_CONSIDERATION_ITEMS:
|
||||||
break
|
break
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|
||||||
|
|
||||||
|
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 []
|
||||||
|
result = []
|
||||||
|
for item in value:
|
||||||
|
if not isinstance(item, str):
|
||||||
|
continue
|
||||||
|
candidate = item.strip()
|
||||||
|
if not candidate or candidate not in allowed:
|
||||||
|
continue
|
||||||
|
if candidate not in result:
|
||||||
|
result.append(candidate)
|
||||||
|
return result
|
||||||
|
|||||||
@ -7,6 +7,8 @@ interface PersonalForm {
|
|||||||
profession: string;
|
profession: string;
|
||||||
tone: string;
|
tone: string;
|
||||||
considerations: string[];
|
considerations: string[];
|
||||||
|
thinking_interval: number | null;
|
||||||
|
disabled_tool_categories: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PersonalizationState {
|
interface PersonalizationState {
|
||||||
@ -23,15 +25,23 @@ interface PersonalizationState {
|
|||||||
tonePresets: string[];
|
tonePresets: string[];
|
||||||
draggedConsiderationIndex: number | null;
|
draggedConsiderationIndex: number | null;
|
||||||
form: PersonalForm;
|
form: PersonalForm;
|
||||||
|
toolCategories: Array<{ id: string; label: string }>;
|
||||||
|
thinkingIntervalDefault: number;
|
||||||
|
thinkingIntervalRange: { min: number; max: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_INTERVAL = 10;
|
||||||
|
const DEFAULT_INTERVAL_RANGE = { min: 1, max: 50 };
|
||||||
|
|
||||||
const defaultForm = (): PersonalForm => ({
|
const defaultForm = (): PersonalForm => ({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
self_identify: '',
|
self_identify: '',
|
||||||
user_name: '',
|
user_name: '',
|
||||||
profession: '',
|
profession: '',
|
||||||
tone: '',
|
tone: '',
|
||||||
considerations: []
|
considerations: [],
|
||||||
|
thinking_interval: null,
|
||||||
|
disabled_tool_categories: []
|
||||||
});
|
});
|
||||||
|
|
||||||
export const usePersonalizationStore = defineStore('personalization', {
|
export const usePersonalizationStore = defineStore('personalization', {
|
||||||
@ -48,7 +58,10 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
newConsideration: '',
|
newConsideration: '',
|
||||||
tonePresets: ['健谈', '幽默', '直言不讳', '鼓励性', '诗意', '企业商务', '打破常规', '同理心'],
|
tonePresets: ['健谈', '幽默', '直言不讳', '鼓励性', '诗意', '企业商务', '打破常规', '同理心'],
|
||||||
draggedConsiderationIndex: null,
|
draggedConsiderationIndex: null,
|
||||||
form: defaultForm()
|
form: defaultForm(),
|
||||||
|
toolCategories: [],
|
||||||
|
thinkingIntervalDefault: DEFAULT_INTERVAL,
|
||||||
|
thinkingIntervalRange: { ...DEFAULT_INTERVAL_RANGE }
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async openDrawer() {
|
async openDrawer() {
|
||||||
@ -91,6 +104,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
throw new Error(result.error || '加载失败');
|
throw new Error(result.error || '加载失败');
|
||||||
}
|
}
|
||||||
this.applyPersonalizationData(result.data || {});
|
this.applyPersonalizationData(result.data || {});
|
||||||
|
this.applyPersonalizationMeta(result);
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.error = error?.message || '加载失败';
|
this.error = error?.message || '加载失败';
|
||||||
@ -105,10 +119,38 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
user_name: data.user_name || '',
|
user_name: data.user_name || '',
|
||||||
profession: data.profession || '',
|
profession: data.profession || '',
|
||||||
tone: data.tone || '',
|
tone: data.tone || '',
|
||||||
considerations: Array.isArray(data.considerations) ? [...data.considerations] : []
|
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') : []
|
||||||
};
|
};
|
||||||
this.clearFeedback();
|
this.clearFeedback();
|
||||||
},
|
},
|
||||||
|
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 && Array.isArray(payload.tool_categories)) {
|
||||||
|
this.toolCategories = payload.tool_categories
|
||||||
|
.map((item: { id?: string; label?: string } = {}) => ({
|
||||||
|
id: typeof item.id === 'string' ? item.id : String(item.id ?? ''),
|
||||||
|
label: (item.label && String(item.label)) || (typeof item.id === 'string' ? item.id : String(item.id ?? ''))
|
||||||
|
}))
|
||||||
|
.filter((item: { id: string }) => !!item.id);
|
||||||
|
} else {
|
||||||
|
this.toolCategories = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
clearFeedback() {
|
clearFeedback() {
|
||||||
this.status = '';
|
this.status = '';
|
||||||
this.error = '';
|
this.error = '';
|
||||||
@ -133,6 +175,10 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
if (!resp.ok || !result.success) {
|
if (!resp.ok || !result.success) {
|
||||||
throw new Error(result.error || '更新失败');
|
throw new Error(result.error || '更新失败');
|
||||||
}
|
}
|
||||||
|
if (result.data) {
|
||||||
|
this.applyPersonalizationData(result.data);
|
||||||
|
}
|
||||||
|
this.applyPersonalizationMeta(result);
|
||||||
const statusLabel = newValue ? '已启用' : '已停用';
|
const statusLabel = newValue ? '已启用' : '已停用';
|
||||||
this.status = statusLabel;
|
this.status = statusLabel;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -165,6 +211,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
throw new Error(result.error || '保存失败');
|
throw new Error(result.error || '保存失败');
|
||||||
}
|
}
|
||||||
this.applyPersonalizationData(result.data || {});
|
this.applyPersonalizationData(result.data || {});
|
||||||
|
this.applyPersonalizationMeta(result);
|
||||||
this.status = '已保存';
|
this.status = '已保存';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.status === '已保存') {
|
if (this.status === '已保存') {
|
||||||
@ -187,6 +234,43 @@ export const usePersonalizationStore = defineStore('personalization', {
|
|||||||
};
|
};
|
||||||
this.clearFeedback();
|
this.clearFeedback();
|
||||||
},
|
},
|
||||||
|
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();
|
||||||
|
},
|
||||||
|
toggleDefaultToolCategory(categoryId: string) {
|
||||||
|
if (!categoryId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const current = new Set(this.form.disabled_tool_categories || []);
|
||||||
|
if (current.has(categoryId)) {
|
||||||
|
current.delete(categoryId);
|
||||||
|
} else {
|
||||||
|
current.add(categoryId);
|
||||||
|
}
|
||||||
|
this.form = {
|
||||||
|
...this.form,
|
||||||
|
disabled_tool_categories: Array.from(current)
|
||||||
|
};
|
||||||
|
this.clearFeedback();
|
||||||
|
},
|
||||||
applyTonePreset(preset: string) {
|
applyTonePreset(preset: string) {
|
||||||
if (!preset) {
|
if (!preset) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user