refactor: 移除人格模式、后台检查、系统诊断、定时任务功能
This commit is contained in:
@@ -15,16 +15,6 @@ import { OllamaAPI } from '../api/ollama.js';
|
||||
import { ChatDB } from '../db/chat-db.js';
|
||||
import type { ChatSession } from '../types.js';
|
||||
|
||||
/** v4.2 人格模式 system prompt 映射 */
|
||||
export const PERSONALITY_SYSTEM_PROMPTS: Record<string, string> = {
|
||||
default: '',
|
||||
coder: '你是一个专业的编程助手。你注重代码质量、可读性和最佳实践。回答时优先给出可运行的代码,其次才是解释。遇到问题先分析根因再给方案。使用 type hints、合理的命名和注释。代码风格偏向简洁紧凑。',
|
||||
writer: '你是一个专业的写作助手。你的文字流畅、有文采,注重结构和节奏。擅长技术文档、文章、文案、邮件等各种文体。回答时先理解写作目的和受众,再给出合适的风格和内容。',
|
||||
analyst: '你是一个精确的数据分析助手。你注重数据的准确性和逻辑的严密性。回答时先明确数据来源和假设,再给出分析过程和结论。偏好用数字和事实说话,避免模糊表述。',
|
||||
translator: '你是一个专业的翻译助手。你精通多语言互译,注重信达雅。翻译时保留原文的语气和风格,专业术语保持一致。遇到歧义时给出多种翻译并标注区别。',
|
||||
reviewer: '你是一个严格的代码审查助手。你关注:正确性(逻辑错误、边界情况)、安全性(注入、泄露)、性能(时间/空间复杂度)、可维护性(命名、结构、注释)。审查时先给出总体评价,再逐项列出问题并给出修复建议。',
|
||||
};
|
||||
|
||||
let settingsModalEl: HTMLElement;
|
||||
|
||||
export function initSettingsModal(): void {
|
||||
@@ -84,84 +74,6 @@ export function initSettingsModal(): void {
|
||||
}, 500);
|
||||
document.querySelector('#inputMaxTurns')!.addEventListener('input', saveMaxTurns);
|
||||
|
||||
// ── v4.2 人格模式切换 ──
|
||||
const PERSONALITY_TEMPERATURES: Record<string, number> = {
|
||||
default: -1, // 使用用户设置
|
||||
coder: 0.3,
|
||||
writer: 0.8,
|
||||
analyst: 0.2,
|
||||
translator: 0.4,
|
||||
reviewer: 0.3,
|
||||
};
|
||||
|
||||
document.querySelectorAll('.personality-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const personality = (btn as HTMLElement).dataset.personality || 'default';
|
||||
state.set('personality', personality);
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
if (db) await db.saveSetting('personality', personality);
|
||||
|
||||
// 更新按钮样式
|
||||
document.querySelectorAll('.personality-btn').forEach(b => {
|
||||
(b as HTMLElement).style.border = '2px solid var(--border-subtle)';
|
||||
b.classList.remove('active');
|
||||
});
|
||||
(btn as HTMLElement).style.border = '2px solid var(--primary)';
|
||||
btn.classList.add('active');
|
||||
|
||||
// 如果人格有推荐温度,自动调整
|
||||
const recommendedTemp = PERSONALITY_TEMPERATURES[personality];
|
||||
if (recommendedTemp >= 0) {
|
||||
const tempSlider = document.querySelector('#inputTemperature') as HTMLInputElement;
|
||||
tempSlider.value = String(recommendedTemp);
|
||||
document.querySelector('#tempValue')!.textContent = recommendedTemp.toFixed(1);
|
||||
state.set('temperature', recommendedTemp);
|
||||
if (db) await db.saveSetting('temperature', recommendedTemp);
|
||||
}
|
||||
|
||||
const names: Record<string, string> = {
|
||||
default: '通用助手', coder: '编程助手', writer: '写作助手',
|
||||
analyst: '数据分析', translator: '翻译助手', reviewer: '代码审查'
|
||||
};
|
||||
showToast(`已切换到 ${names[personality] || personality}`, 'success');
|
||||
logSetting('人格模式', names[personality] || personality);
|
||||
});
|
||||
});
|
||||
|
||||
// ── v4.3 诊断系统 ──
|
||||
document.querySelector('#btnDoctor')!.addEventListener('click', runDoctor);
|
||||
|
||||
// ── v4.3 Heartbeat 后台检查 ──
|
||||
const toggleHB = document.querySelector<HTMLInputElement>('#toggleHeartbeat')!;
|
||||
const selectHBInterval = document.querySelector<HTMLSelectElement>('#selectHeartbeatInterval')!;
|
||||
|
||||
toggleHB.addEventListener('change', async () => {
|
||||
const enabled = toggleHB.checked;
|
||||
state.set('heartbeatEnabled', enabled);
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
if (db) await db.saveSetting('heartbeatEnabled', enabled);
|
||||
if (enabled) {
|
||||
startHeartbeat(parseInt(selectHBInterval.value));
|
||||
showToast('后台检查已启用', 'success');
|
||||
} else {
|
||||
stopHeartbeat();
|
||||
showToast('后台检查已关闭', 'info');
|
||||
}
|
||||
logSetting('后台检查', enabled ? '已启用' : '已关闭');
|
||||
});
|
||||
|
||||
selectHBInterval.addEventListener('change', async () => {
|
||||
const ms = parseInt(selectHBInterval.value);
|
||||
state.set('heartbeatInterval', ms);
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
if (db) await db.saveSetting('heartbeatInterval', ms);
|
||||
if (state.get<boolean>('heartbeatEnabled', false)) {
|
||||
stopHeartbeat();
|
||||
startHeartbeat(ms);
|
||||
}
|
||||
logSetting('检查间隔', `${ms / 60000} 分钟`);
|
||||
});
|
||||
|
||||
// ── v5.0 MCP 服务器管理 ──
|
||||
async function renderMCPServerList(): Promise<void> {
|
||||
const { getMCPServers, getMCPServerStatuses, toggleMCPServer, removeMCPServer } = await import('../services/mcp-client.js');
|
||||
@@ -234,33 +146,6 @@ export function initSettingsModal(): void {
|
||||
setTimeout(renderMCPServerList, 100);
|
||||
});
|
||||
|
||||
// ── v5.0 Cron 定时任务 ──
|
||||
document.querySelector('#btnAddCronTask')!.addEventListener('click', async () => {
|
||||
const name = (document.querySelector('#inputCronName') as HTMLInputElement).value.trim();
|
||||
const type = (document.querySelector('#selectCronType') as HTMLSelectElement).value as 'oneshot' | 'recurring';
|
||||
const schedule = (document.querySelector('#inputCronSchedule') as HTMLInputElement).value.trim();
|
||||
const message = (document.querySelector('#inputCronMessage') as HTMLInputElement).value.trim();
|
||||
|
||||
if (!name || !schedule || !message) {
|
||||
showToast('请填写任务名称、时间和描述', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const { addCronTask } = await import('../services/cron-manager.js');
|
||||
await addCronTask({ name, type, schedule, message, enabled: true });
|
||||
|
||||
// 清空输入框
|
||||
(document.querySelector('#inputCronName') as HTMLInputElement).value = '';
|
||||
(document.querySelector('#inputCronSchedule') as HTMLInputElement).value = '';
|
||||
(document.querySelector('#inputCronMessage') as HTMLInputElement).value = '';
|
||||
|
||||
showToast(`定时任务已添加: ${name}`, 'success');
|
||||
renderCronTaskList();
|
||||
});
|
||||
|
||||
// 初始化 cron 列表
|
||||
renderCronTaskList();
|
||||
|
||||
document.querySelector('#btnReleaseVRAM')!.addEventListener('click', async () => {
|
||||
const api = state.get<OllamaAPI>(KEYS.API);
|
||||
const model = (document.querySelector('#modelSelect') as HTMLSelectElement).value;
|
||||
@@ -547,185 +432,3 @@ async function importSessions(filePath: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// v4.3 诊断系统
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
async function runDoctor(): Promise<void> {
|
||||
const resultsEl = document.querySelector('#doctorResults')!;
|
||||
resultsEl.style.display = '';
|
||||
resultsEl.innerHTML = '<div>🩺 正在诊断...</div>';
|
||||
|
||||
const checks: Array<{ name: string; status: 'ok' | 'warn' | 'error'; detail: string }> = [];
|
||||
|
||||
// 1. Ollama 连接
|
||||
const api = state.get<OllamaAPI>(KEYS.API);
|
||||
try {
|
||||
const version = await api!.getVersion();
|
||||
checks.push({ name: 'Ollama 连接', status: 'ok', detail: `v${version.version}` });
|
||||
} catch (err) {
|
||||
checks.push({ name: 'Ollama 连接', status: 'error', detail: `无法连接: ${(err as Error).message}` });
|
||||
}
|
||||
|
||||
// 2. 模型可用性
|
||||
try {
|
||||
const models = await api!.listModels();
|
||||
const count = models.models?.length || 0;
|
||||
checks.push({ name: '已安装模型', status: count > 0 ? 'ok' : 'warn', detail: `${count} 个模型` });
|
||||
} catch {
|
||||
checks.push({ name: '已安装模型', status: 'error', detail: '无法获取模型列表' });
|
||||
}
|
||||
|
||||
// 3. 当前模型能力
|
||||
const model = (document.querySelector('#modelSelect') as HTMLSelectElement)?.value;
|
||||
if (model) {
|
||||
try {
|
||||
const detail = await api!.showModel(model);
|
||||
const caps = detail.capabilities || [];
|
||||
const hasTools = caps.includes('tools');
|
||||
const hasVision = caps.includes('vision');
|
||||
const hasThink = caps.includes('thinking');
|
||||
checks.push({
|
||||
name: `模型能力 (${model})`,
|
||||
status: hasTools ? 'ok' : 'warn',
|
||||
detail: `Tool Calling: ${hasTools ? '✅' : '❌'} | Vision: ${hasVision ? '✅' : '❌'} | Think: ${hasThink ? '✅' : '❌'}`
|
||||
});
|
||||
} catch {
|
||||
checks.push({ name: '模型能力', status: 'warn', detail: '无法检测模型能力' });
|
||||
}
|
||||
} else {
|
||||
checks.push({ name: '当前模型', status: 'warn', detail: '未选择模型' });
|
||||
}
|
||||
|
||||
// 4. SQLite 数据库
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
if (db) {
|
||||
try {
|
||||
const sessions = await db.getAllSessions();
|
||||
checks.push({ name: 'SQLite 数据库', status: 'ok', detail: `${sessions.length} 个会话` });
|
||||
} catch (err) {
|
||||
checks.push({ name: 'SQLite 数据库', status: 'error', detail: `错误: ${(err as Error).message}` });
|
||||
}
|
||||
} else {
|
||||
checks.push({ name: 'SQLite 数据库', status: 'error', detail: '未初始化' });
|
||||
}
|
||||
|
||||
// 5. 技能系统
|
||||
try {
|
||||
if (db) {
|
||||
checks.push({ name: '技能系统', status: 'ok', detail: '已就绪' });
|
||||
}
|
||||
} catch {
|
||||
checks.push({ name: '技能系统', status: 'warn', detail: '无法获取技能列表' });
|
||||
}
|
||||
|
||||
// 6. 版本信息
|
||||
const bridge = window.metonaDesktop;
|
||||
if (bridge?.info) {
|
||||
try {
|
||||
const info = await bridge.info();
|
||||
checks.push({ name: '应用信息', status: 'ok', detail: `v${info.version} | Electron ${info.electronVersion} | ${info.platform} ${info.arch}` });
|
||||
} catch {
|
||||
checks.push({ name: '应用信息', status: 'warn', detail: '无法获取' });
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染结果
|
||||
const statusIcon = (s: string) => s === 'ok' ? '✅' : s === 'warn' ? '⚠️' : '❌';
|
||||
const allOk = checks.every(c => c.status === 'ok');
|
||||
|
||||
resultsEl.innerHTML = `
|
||||
<div style="margin-bottom:6px;font-weight:600;">${allOk ? '✅ 所有检查通过' : '⚠️ 发现问题'}</div>
|
||||
${checks.map(c => `<div style="margin:4px 0;">${statusIcon(c.status)} <strong>${c.name}</strong>: ${c.detail}</div>`).join('')}
|
||||
`;
|
||||
|
||||
logInfo('系统诊断完成', `${checks.filter(c => c.status === 'ok').length}/${checks.length} 项通过`);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// v4.3 Heartbeat 后台检查
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
export function startHeartbeat(intervalMs: number): void {
|
||||
stopHeartbeat();
|
||||
heartbeatTimer = setInterval(async () => {
|
||||
const api = state.get<OllamaAPI>(KEYS.API);
|
||||
if (!api) return;
|
||||
|
||||
try {
|
||||
await api.getVersion();
|
||||
// 连接正常,静默
|
||||
} catch {
|
||||
// 连接异常,通知用户
|
||||
const bridge = window.metonaDesktop;
|
||||
if (bridge?.notify) {
|
||||
bridge.notify('Metona Ollama', '⚠️ Ollama 服务连接异常,请检查是否正在运行');
|
||||
}
|
||||
logWarn('Heartbeat: Ollama 连接异常');
|
||||
}
|
||||
}, intervalMs);
|
||||
logInfo(`Heartbeat 已启动`, `间隔: ${intervalMs / 60000} 分钟`);
|
||||
}
|
||||
|
||||
export function stopHeartbeat(): void {
|
||||
if (heartbeatTimer) {
|
||||
clearInterval(heartbeatTimer);
|
||||
heartbeatTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 应用启动时恢复 heartbeat */
|
||||
export function restoreHeartbeat(): void {
|
||||
const enabled = state.get<boolean>('heartbeatEnabled', false);
|
||||
const interval = state.get<number>('heartbeatInterval', 3600000);
|
||||
if (enabled) {
|
||||
startHeartbeat(interval);
|
||||
// 同步 UI 状态
|
||||
const toggle = document.querySelector<HTMLInputElement>('#toggleHeartbeat');
|
||||
if (toggle) toggle.checked = true;
|
||||
const select = document.querySelector<HTMLSelectElement>('#selectHeartbeatInterval');
|
||||
if (select) select.value = String(interval);
|
||||
}
|
||||
}
|
||||
|
||||
/** 渲染定时任务列表 */
|
||||
async function renderCronTaskList(): Promise<void> {
|
||||
const container = document.querySelector('#cronTaskList');
|
||||
if (!container) return;
|
||||
|
||||
const { getCronTasks, removeCronTask, toggleCronTask } = await import('../services/cron-manager.js');
|
||||
const tasks = await getCronTasks();
|
||||
|
||||
if (tasks.length === 0) {
|
||||
container.innerHTML = '<p class="text-muted">无定时任务</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = tasks.map(t => `
|
||||
<div style="display:flex;align-items:center;gap:6px;padding:4px 0;border-bottom:1px solid var(--border-subtle);">
|
||||
<span>${t.enabled ? '✅' : '⏸️'}</span>
|
||||
<span style="flex:1;font-weight:500;">${t.name}</span>
|
||||
<span class="text-muted" style="font-size:11px;">${t.type === 'oneshot' ? '一次性' : '周期'}</span>
|
||||
<button class="btn btn-sm" onclick="window.__toggleCron('${t.id}')" style="padding:2px 6px;font-size:11px;">${t.enabled ? '禁用' : '启用'}</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="window.__deleteCron('${t.id}')" style="padding:2px 6px;font-size:11px;">删除</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 全局方法供内联 onclick 调用
|
||||
interface WindowWithCron extends Window {
|
||||
__toggleCron?: (id: string) => Promise<void>;
|
||||
__deleteCron?: (id: string) => Promise<void>;
|
||||
}
|
||||
(window as WindowWithCron).__toggleCron = async (id: string) => {
|
||||
const { toggleCronTask } = await import('../services/cron-manager.js');
|
||||
await toggleCronTask(id);
|
||||
renderCronTaskList();
|
||||
};
|
||||
(window as WindowWithCron).__deleteCron = async (id: string) => {
|
||||
const { removeCronTask } = await import('../services/cron-manager.js');
|
||||
await removeCronTask(id);
|
||||
renderCronTaskList();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user