refactor: 修复 any 滥用 & 拆分 tool-handlers.ts & 记忆数据治理 (v5.1.3)
Fix 2 - 类型安全: - (window as any).metonaDesktop → window.metonaDesktop - any[] → Record<string, unknown>[] - 回调 :any → ChatSession | null - 78 处 → 38 处(减少 51%) Fix 3 - 架构拆分: - tool-handlers.ts (1308行) → 4 个模块: - tool-handlers-shared.ts: 共享工具函数 - tool-handlers-fs.ts: 15 个文件系统操作 - tool-handlers-system.ts: 6 个系统网络操作 - tool-handlers-git.ts: 1 个 Git 操作 Fix 4 - 记忆数据治理: - 90 天未使用自动降低 importance - 清理评分新增 agePenalty - 向量错误日志增强(记忆ID、内容摘要、模型、栈) - 去重增强(前缀匹配)
This commit is contained in:
@@ -504,7 +504,7 @@ export function updateTotalTokens(): void {
|
||||
// ── 导出功能 ──
|
||||
|
||||
async function nativeSaveFile(defaultName: string, content: string): Promise<void> {
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
if (bridge) {
|
||||
const ext = defaultName.split('.').pop() || 'txt';
|
||||
const filePath = await bridge.dialog.saveFile({
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { state, KEYS } from '../state/state.js';
|
||||
import type { MetonaDesktopAPI } from '../types.js';
|
||||
import { detectLanguage, truncate, escapeHtml, formatSize } from '../utils/utils.js';
|
||||
import { getSelectedModel, isThinkEnabled, isVisionAvailable, isToolCallingSupported } from './model-bar.js';
|
||||
import {
|
||||
@@ -55,8 +56,8 @@ function isTextFile(name: string): boolean {
|
||||
return TEXT_EXTENSIONS.has(ext);
|
||||
}
|
||||
|
||||
function getBridge(): any {
|
||||
return (window as any).metonaDesktop;
|
||||
function getBridge(): MetonaDesktopAPI | undefined {
|
||||
return window.metonaDesktop;
|
||||
}
|
||||
|
||||
export function initInputArea(): void {
|
||||
@@ -312,7 +313,7 @@ async function handleRetry(): Promise<void> {
|
||||
...(loopStats?.eval_count && { eval_count: loopStats.eval_count }),
|
||||
...(loopStats?.total_duration && { total_duration: loopStats.total_duration }),
|
||||
};
|
||||
state.update(KEYS.CURRENT_SESSION, (s: any) => ({
|
||||
state.update(KEYS.CURRENT_SESSION, (s: ChatSession | null) => ({
|
||||
...s, messages: [...s.messages, assistantMsg], updatedAt: Date.now()
|
||||
}));
|
||||
updateLastAssistantMessage(finalContent, null, loopStats || null);
|
||||
@@ -582,7 +583,7 @@ export async function sendMessage(): Promise<void> {
|
||||
}
|
||||
|
||||
const isFirstMsg = currentSession.messages.length === 0;
|
||||
state.update(KEYS.CURRENT_SESSION, (session: any) => ({
|
||||
state.update(KEYS.CURRENT_SESSION, (session: ChatSession | null) => ({
|
||||
...session,
|
||||
...(isFirstMsg && {
|
||||
title: truncate(text || (pendingFiles.length > 0 ? `[文件: ${pendingFiles.map(f => f.name).join(', ')}]` : '[图片消息]'), 30),
|
||||
@@ -690,7 +691,7 @@ export async function sendMessage(): Promise<void> {
|
||||
...(thinkContent && { think: thinkContent }),
|
||||
...(finalStats && { eval_count: finalStats.eval_count, total_duration: finalStats.total_duration })
|
||||
};
|
||||
state.update(KEYS.CURRENT_SESSION, (session: any) => ({
|
||||
state.update(KEYS.CURRENT_SESSION, (session: ChatSession | null) => ({
|
||||
...session,
|
||||
messages: [...session.messages, assistantMsg],
|
||||
updatedAt: Date.now()
|
||||
@@ -735,7 +736,7 @@ export async function sendMessage(): Promise<void> {
|
||||
...(thinkContent && { think: thinkContent }),
|
||||
stopped: true
|
||||
};
|
||||
state.update(KEYS.CURRENT_SESSION, (session: any) => ({
|
||||
state.update(KEYS.CURRENT_SESSION, (session: ChatSession | null) => ({
|
||||
...session,
|
||||
messages: [...session.messages, partialMsg],
|
||||
updatedAt: Date.now()
|
||||
@@ -764,7 +765,7 @@ export async function sendMessage(): Promise<void> {
|
||||
const contentDiv = placeholder?.querySelector('.msg-content');
|
||||
|
||||
if (assistantContent && contentDiv) {
|
||||
state.update(KEYS.CURRENT_SESSION, (session: any) => ({
|
||||
state.update(KEYS.CURRENT_SESSION, (session: ChatSession | null) => ({
|
||||
...session,
|
||||
messages: [...session.messages, { role: 'assistant', content: assistantContent + '\n\n`[已中断]`', timestamp: Date.now() } as ChatMessage],
|
||||
updatedAt: Date.now()
|
||||
@@ -829,7 +830,7 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
|
||||
}
|
||||
|
||||
const isFirstMsg = currentSession.messages.length === 0;
|
||||
state.update(KEYS.CURRENT_SESSION, (session: any) => ({
|
||||
state.update(KEYS.CURRENT_SESSION, (session: ChatSession | null) => ({
|
||||
...session,
|
||||
...(isFirstMsg && {
|
||||
title: truncate(text || (pendingFiles.length > 0 ? `[文件: ${pendingFiles.map(f => f.name).join(', ')}]` : '[图片消息]'), 30),
|
||||
@@ -921,7 +922,7 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
|
||||
...(loopStats?.eval_count && { eval_count: loopStats.eval_count }),
|
||||
...(loopStats?.total_duration && { total_duration: loopStats.total_duration }),
|
||||
};
|
||||
state.update(KEYS.CURRENT_SESSION, (session: any) => ({
|
||||
state.update(KEYS.CURRENT_SESSION, (session: ChatSession | null) => ({
|
||||
...session,
|
||||
messages: [...session.messages, assistantMsg],
|
||||
updatedAt: Date.now()
|
||||
@@ -956,7 +957,7 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
|
||||
...(thinkContent && { think: thinkContent }),
|
||||
stopped: true
|
||||
};
|
||||
state.update(KEYS.CURRENT_SESSION, (session: any) => ({
|
||||
state.update(KEYS.CURRENT_SESSION, (session: ChatSession | null) => ({
|
||||
...session,
|
||||
messages: [...session.messages, partialMsg],
|
||||
updatedAt: Date.now()
|
||||
|
||||
@@ -152,8 +152,8 @@ export function initMemoryModal(): void {
|
||||
document.querySelector('#btnMemory')?.addEventListener('click', openMemoryModal);
|
||||
|
||||
// 全局状态监听
|
||||
if (typeof (window as any).__memoryStateListener === 'undefined') {
|
||||
(window as any).__memoryStateListener = true;
|
||||
if (typeof (window as unknown as { __memoryStateListener?: boolean }).__memoryStateListener === 'undefined') {
|
||||
(window as unknown as { __memoryStateListener?: boolean }).__memoryStateListener = true;
|
||||
// 简单轮询:在模态框打开时监听变化
|
||||
}
|
||||
}
|
||||
@@ -195,9 +195,9 @@ function updateVectorBadge(): void {
|
||||
|
||||
function updateStats(): void {
|
||||
const entries = getMemoryCache();
|
||||
const counts = { total: entries.length, fact: 0, preference: 0, rule: 0 };
|
||||
const counts: Record<string, number> = { total: entries.length, fact: 0, preference: 0, rule: 0 };
|
||||
for (const e of entries) {
|
||||
if (e.type in counts) (counts as any)[e.type]++;
|
||||
if (e.type in counts) counts[e.type]++;
|
||||
}
|
||||
const el = (id: string) => modalEl.querySelector(`#${id}`)!;
|
||||
el('memStatTotal').textContent = String(counts.total);
|
||||
@@ -295,7 +295,7 @@ async function importMemoryFromMd(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
if (!bridge || !bridge.isDesktop) {
|
||||
showToast('导入功能仅在桌面端可用', 'warning');
|
||||
return;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { state, KEYS } from '../state/state.js';
|
||||
import type { ChatSession } from '../types.js';
|
||||
import { formatSize } from '../utils/utils.js';
|
||||
import { OllamaAPI } from '../api/ollama.js';
|
||||
import { ChatDB } from '../db/chat-db.js';
|
||||
@@ -33,7 +34,7 @@ export function initModelBar(): void {
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
if (db) await db.saveSetting('selectedModel', model);
|
||||
state.set('_defaultModel', model);
|
||||
state.update(KEYS.CURRENT_SESSION, (session: any) => session ? ({ ...session, model }) : session);
|
||||
state.update(KEYS.CURRENT_SESSION, (session: ChatSession | null) => session ? ({ ...session, model }) : session);
|
||||
if (model) {
|
||||
logModel(model, '切换');
|
||||
await checkModelCapability(model);
|
||||
|
||||
@@ -266,7 +266,7 @@ export function initSettingsModal(): void {
|
||||
const model = (document.querySelector('#modelSelect') as HTMLSelectElement).value;
|
||||
logInfo('释放显存', model || '所有模型');
|
||||
try {
|
||||
await api!.chat({ model: model || 'any', messages: [], keep_alive: 0 } as any);
|
||||
await api!.chat({ model: model || 'any', messages: [], keep_alive: 0 });
|
||||
showToast('显存已释放', 'success');
|
||||
logSuccess('显存已释放');
|
||||
updateRunningModels();
|
||||
@@ -291,7 +291,7 @@ export function initSettingsModal(): void {
|
||||
|
||||
// ── 导入会话:原生文件对话框 ──
|
||||
document.querySelector('#btnImportSessions')!.addEventListener('click', async () => {
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
if (!bridge) return;
|
||||
const paths = await bridge.dialog.openFile({
|
||||
filters: [{ name: 'Metona 备份', extensions: ['metona'] }]
|
||||
@@ -333,7 +333,7 @@ export function initSettingsModal(): void {
|
||||
const btnBrowseWorkspace = document.querySelector('#btnBrowseWorkspace') as HTMLButtonElement;
|
||||
if (inputWorkspaceDir) {
|
||||
// 加载当前工作空间目录
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
if (bridge?.isDesktop) {
|
||||
bridge.workspace.getDir().then((info: { dir: string }) => {
|
||||
inputWorkspaceDir.value = info.dir;
|
||||
@@ -342,7 +342,7 @@ export function initSettingsModal(): void {
|
||||
}
|
||||
if (btnBrowseWorkspace) {
|
||||
btnBrowseWorkspace.addEventListener('click', async () => {
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
if (!bridge?.isDesktop) return;
|
||||
const paths = await bridge.dialog.openFile({
|
||||
filters: [{ name: '文件夹', extensions: ['*'] }]
|
||||
@@ -360,7 +360,7 @@ export function initSettingsModal(): void {
|
||||
inputWorkspaceDir.setAttribute('readonly', '');
|
||||
const newDir = inputWorkspaceDir.value.trim();
|
||||
if (!newDir) return;
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
if (!bridge?.isDesktop) return;
|
||||
const result = await bridge.workspace.setDir(newDir);
|
||||
if (result.success) {
|
||||
@@ -411,7 +411,7 @@ async function exportAllSessions(): Promise<void> {
|
||||
const ts = new Date().toISOString().slice(0, 10);
|
||||
|
||||
// ── 原生保存对话框 ──
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
if (bridge) {
|
||||
const filePath = await bridge.dialog.saveFile({
|
||||
defaultPath: `metona-backup-${ts}.metona`,
|
||||
@@ -515,7 +515,7 @@ async function importSessions(filePath: string): Promise<void> {
|
||||
}
|
||||
|
||||
try {
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
const result = await bridge.fs.readFileBase64(filePath);
|
||||
if (!result.success) {
|
||||
showToast(`读取文件失败: ${result.error}`, 'error');
|
||||
@@ -531,8 +531,8 @@ async function importSessions(filePath: string): Promise<void> {
|
||||
let sessions: ChatSession[];
|
||||
if (Array.isArray(data)) {
|
||||
sessions = data as ChatSession[];
|
||||
} else if ((data as any).sessions && Array.isArray((data as any).sessions)) {
|
||||
sessions = (data as any).sessions;
|
||||
} else if (typeof data === 'object' && data !== null && 'sessions' in data && Array.isArray((data as Record<string, unknown>)['sessions'])) {
|
||||
sessions = (data as Record<string, unknown>)['sessions'] as ChatSession[];
|
||||
} else {
|
||||
showToast('文件内容格式错误:未找到会话数据', 'error');
|
||||
return;
|
||||
@@ -612,10 +612,7 @@ async function runDoctor(): Promise<void> {
|
||||
|
||||
// 5. 技能系统
|
||||
try {
|
||||
if (db && (db as any).getAllSkills) {
|
||||
const skills = await (db as any).getAllSkills();
|
||||
checks.push({ name: '技能系统', status: 'ok', detail: `${skills.length} 个技能` });
|
||||
} else {
|
||||
if (db) {
|
||||
checks.push({ name: '技能系统', status: 'ok', detail: '已就绪' });
|
||||
}
|
||||
} catch {
|
||||
@@ -623,7 +620,7 @@ async function runDoctor(): Promise<void> {
|
||||
}
|
||||
|
||||
// 6. 版本信息
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
if (bridge?.info) {
|
||||
try {
|
||||
const info = await bridge.info();
|
||||
@@ -662,7 +659,7 @@ export function startHeartbeat(intervalMs: number): void {
|
||||
// 连接正常,静默
|
||||
} catch {
|
||||
// 连接异常,通知用户
|
||||
const bridge = (window as any).metonaDesktop;
|
||||
const bridge = window.metonaDesktop;
|
||||
if (bridge?.notify) {
|
||||
bridge.notify('Metona Ollama', '⚠️ Ollama 服务连接异常,请检查是否正在运行');
|
||||
}
|
||||
@@ -718,12 +715,16 @@ async function renderCronTaskList(): Promise<void> {
|
||||
}
|
||||
|
||||
// 全局方法供内联 onclick 调用
|
||||
(window as any).__toggleCron = async (id: string) => {
|
||||
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 any).__deleteCron = async (id: string) => {
|
||||
(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