**崩溃/挂死修复 (5):** - 统一 TrayManager.isQuitting 变量,修复 Cmd+Q 无法退出 - useAgentStream 闭包过期快照 → 每次 getState() - Agnes chatStream 添加 AbortSignal.timeout - SSE JSON.parse 添加 try-catch 保护 - Orchestrator setTools 污染 → save/restore 模式 **功能修复 (14):** - 上下文压缩实现 (每5轮 COMPRESSING 状态) - 修复 requestId 硬编码空串 - ConfigService.set() 保留已有 category - MemoryManager 新增 working 类型搜索 - PromptInjectionDefender 补全 sanitize() - Ollama: 补全 dynamicReminders + reasoningContent - openai-format: 所有 assistant 消息保留 reasoningContent - SSE: finish_reason 时提前 flush tool_calls - DeepSeek thinking effort 映射注释 - Ollama done_reason load→stop - RateLimitHook >= 边界修复 - WorkspaceService isValid 首次启动修复 - sessions:archive IPC handler - 托盘/窗口图标路径生产环境修复 **系统提示词优化:** - SOUL.md 存在时不显示兜底身份,原文放最前 - 兜底身份改为中文 (MetonaAI 自身描述) - 用户文本在前,附件内容在后 **文件上传:** - 非图片文件不再 base64 编码,保留 JSON 结构 - 用户文本优先于文件内容 **UI 修复:** - 首页 Logo 路径修复 (public/ + 相对路径) - TokenUsage contextWindow 动态计算 (Provider 感知) - 切换 Provider 同步 contextWindow - 托盘图标始终显示 Logo (状态由右键菜单展示)
105 lines
2.6 KiB
TypeScript
105 lines
2.6 KiB
TypeScript
/**
|
|
* Config Service — 配置读写
|
|
*
|
|
* 管理 app_config 表的读写操作。
|
|
* 所有配置值以 JSON 格式存储。
|
|
*
|
|
* @see docs/MetonaAI-Desktop 架构与交互设计.html — 数据库配置
|
|
*/
|
|
|
|
import type Database from 'better-sqlite3';
|
|
import log from 'electron-log';
|
|
|
|
export interface ConfigRow {
|
|
key: string;
|
|
value: string;
|
|
category: string;
|
|
updated_at: number;
|
|
}
|
|
|
|
export class ConfigService {
|
|
constructor(private getDB: () => Database.Database) {}
|
|
|
|
/**
|
|
* 获取配置值
|
|
*/
|
|
get<T = unknown>(key: string): T | null {
|
|
const db = this.getDB();
|
|
const row = db.prepare('SELECT value FROM app_config WHERE key = ?').get(key) as ConfigRow | undefined;
|
|
|
|
if (!row) return null;
|
|
|
|
try {
|
|
return JSON.parse(row.value) as T;
|
|
} catch {
|
|
return row.value as T;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 设置配置值(保留已有 category)
|
|
*/
|
|
set(key: string, value: unknown): void {
|
|
const db = this.getDB();
|
|
const jsonValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
|
|
// 查询已有 category,避免 INSERT OR REPLACE 覆盖为默认值
|
|
const existing = db.prepare('SELECT category FROM app_config WHERE key = ?').get(key) as ConfigRow | undefined;
|
|
const category = existing?.category ?? 'general';
|
|
|
|
db.prepare(`
|
|
INSERT OR REPLACE INTO app_config (key, value, category, updated_at)
|
|
VALUES (?, ?, ?, ?)
|
|
`).run(key, jsonValue, category, Date.now());
|
|
|
|
log.debug(`Config set: ${key}`);
|
|
}
|
|
|
|
/**
|
|
* 获取所有配置
|
|
*/
|
|
getAll(): Record<string, unknown> {
|
|
const db = this.getDB();
|
|
const rows = db.prepare('SELECT key, value FROM app_config').all() as ConfigRow[];
|
|
|
|
const config: Record<string, unknown> = {};
|
|
for (const row of rows) {
|
|
try {
|
|
config[row.key] = JSON.parse(row.value);
|
|
} catch {
|
|
config[row.key] = row.value;
|
|
}
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* 获取指定分类的所有配置
|
|
*/
|
|
getByCategory(category: string): Record<string, unknown> {
|
|
const db = this.getDB();
|
|
const rows = db.prepare('SELECT key, value FROM app_config WHERE category = ?').all(category) as ConfigRow[];
|
|
|
|
const config: Record<string, unknown> = {};
|
|
for (const row of rows) {
|
|
try {
|
|
config[row.key] = JSON.parse(row.value);
|
|
} catch {
|
|
config[row.key] = row.value;
|
|
}
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* 删除配置
|
|
*/
|
|
delete(key: string): boolean {
|
|
const db = this.getDB();
|
|
const result = db.prepare('DELETE FROM app_config WHERE key = ?').run(key);
|
|
return result.changes > 0;
|
|
}
|
|
}
|