feat: persist behavior preferences
This commit is contained in:
parent
2847ade631
commit
02ab023ad7
@ -4,7 +4,7 @@ import asyncio
|
||||
import json
|
||||
from datetime import datetime
|
||||
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
|
||||
|
||||
try:
|
||||
@ -54,6 +54,10 @@ from modules.personalization_manager import (
|
||||
load_personalization_config,
|
||||
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 core.tool_config import TOOL_CATEGORIES
|
||||
from utils.api_client import DeepSeekClient
|
||||
@ -133,6 +137,9 @@ class MainTerminal:
|
||||
self.disabled_tools = set()
|
||||
self.disabled_notice_tools = set()
|
||||
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()
|
||||
@ -319,6 +326,33 @@ class MainTerminal:
|
||||
self.context_manager.add_conversation("system", message, metadata=metadata)
|
||||
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:
|
||||
"""集中处理 read_file 工具的三种模式。"""
|
||||
|
||||
@ -7,11 +7,20 @@ from copy import deepcopy
|
||||
from pathlib import Path
|
||||
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"
|
||||
MAX_SHORT_FIELD_LENGTH = 20
|
||||
MAX_CONSIDERATION_LENGTH = 50
|
||||
MAX_CONSIDERATION_ITEMS = 10
|
||||
TONE_PRESETS = ["健谈", "幽默", "直言不讳", "鼓励性", "诗意", "企业商务", "打破常规", "同理心"]
|
||||
THINKING_INTERVAL_MIN = 1
|
||||
THINKING_INTERVAL_MAX = 50
|
||||
|
||||
DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
|
||||
"enabled": False,
|
||||
@ -20,6 +29,8 @@ DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = {
|
||||
"profession": "",
|
||||
"tone": "",
|
||||
"considerations": [],
|
||||
"thinking_interval": None,
|
||||
"disabled_tool_categories": [],
|
||||
}
|
||||
|
||||
__all__ = [
|
||||
@ -86,6 +97,7 @@ def sanitize_personalization_payload(
|
||||
if fallback:
|
||||
base.update(fallback)
|
||||
data = payload or {}
|
||||
allowed_tool_categories = set(TOOL_CATEGORIES.keys())
|
||||
|
||||
def _resolve_short_field(key: str) -> str:
|
||||
if key in data:
|
||||
@ -101,6 +113,16 @@ def sanitize_personalization_payload(
|
||||
base["considerations"] = _sanitize_considerations(data.get("considerations"))
|
||||
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 "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
|
||||
|
||||
|
||||
@ -172,3 +194,31 @@ def _sanitize_considerations(value: Any) -> list:
|
||||
if len(cleaned) >= MAX_CONSIDERATION_ITEMS:
|
||||
break
|
||||
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;
|
||||
tone: string;
|
||||
considerations: string[];
|
||||
thinking_interval: number | null;
|
||||
disabled_tool_categories: string[];
|
||||
}
|
||||
|
||||
interface PersonalizationState {
|
||||
@ -23,15 +25,23 @@ interface PersonalizationState {
|
||||
tonePresets: string[];
|
||||
draggedConsiderationIndex: number | null;
|
||||
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 => ({
|
||||
enabled: false,
|
||||
self_identify: '',
|
||||
user_name: '',
|
||||
profession: '',
|
||||
tone: '',
|
||||
considerations: []
|
||||
considerations: [],
|
||||
thinking_interval: null,
|
||||
disabled_tool_categories: []
|
||||
});
|
||||
|
||||
export const usePersonalizationStore = defineStore('personalization', {
|
||||
@ -48,7 +58,10 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
newConsideration: '',
|
||||
tonePresets: ['健谈', '幽默', '直言不讳', '鼓励性', '诗意', '企业商务', '打破常规', '同理心'],
|
||||
draggedConsiderationIndex: null,
|
||||
form: defaultForm()
|
||||
form: defaultForm(),
|
||||
toolCategories: [],
|
||||
thinkingIntervalDefault: DEFAULT_INTERVAL,
|
||||
thinkingIntervalRange: { ...DEFAULT_INTERVAL_RANGE }
|
||||
}),
|
||||
actions: {
|
||||
async openDrawer() {
|
||||
@ -91,6 +104,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
throw new Error(result.error || '加载失败');
|
||||
}
|
||||
this.applyPersonalizationData(result.data || {});
|
||||
this.applyPersonalizationMeta(result);
|
||||
this.loaded = true;
|
||||
} catch (error: any) {
|
||||
this.error = error?.message || '加载失败';
|
||||
@ -105,10 +119,38 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
user_name: data.user_name || '',
|
||||
profession: data.profession || '',
|
||||
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();
|
||||
},
|
||||
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() {
|
||||
this.status = '';
|
||||
this.error = '';
|
||||
@ -133,6 +175,10 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
if (!resp.ok || !result.success) {
|
||||
throw new Error(result.error || '更新失败');
|
||||
}
|
||||
if (result.data) {
|
||||
this.applyPersonalizationData(result.data);
|
||||
}
|
||||
this.applyPersonalizationMeta(result);
|
||||
const statusLabel = newValue ? '已启用' : '已停用';
|
||||
this.status = statusLabel;
|
||||
setTimeout(() => {
|
||||
@ -165,6 +211,7 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
throw new Error(result.error || '保存失败');
|
||||
}
|
||||
this.applyPersonalizationData(result.data || {});
|
||||
this.applyPersonalizationMeta(result);
|
||||
this.status = '已保存';
|
||||
setTimeout(() => {
|
||||
if (this.status === '已保存') {
|
||||
@ -187,6 +234,43 @@ export const usePersonalizationStore = defineStore('personalization', {
|
||||
};
|
||||
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) {
|
||||
if (!preset) {
|
||||
return;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user