From 1f99d61a12357cf927e7388dae842309e602c91e Mon Sep 17 00:00:00 2001 From: thzxx Date: Sun, 19 Apr 2026 17:10:42 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=97=A5=E5=BF=97=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E7=A7=BB=E8=87=B3=E6=97=A5=E5=BF=97=E9=A1=B6=E6=A0=8F,=20?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=BC=95=E6=93=8EBing=E4=BC=98=E5=85=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/tool-handlers.ts | 24 +++++----- src/renderer/components/settings-modal.ts | 56 +---------------------- src/renderer/services/log-service.ts | 44 +++++++++++++++++- 3 files changed, 56 insertions(+), 68 deletions(-) diff --git a/src/main/tool-handlers.ts b/src/main/tool-handlers.ts index 9e86110..6822abd 100644 --- a/src/main/tool-handlers.ts +++ b/src/main/tool-handlers.ts @@ -489,7 +489,7 @@ export async function handleWebFetch(params: { url: string; max_chars?: number; /** * Web Search — 联网搜索 - * 使用百度搜索(国内可用)+ Bing 作为备用 + * 使用 Bing 搜索(首选)+ 百度作为备用 */ export async function handleWebSearch(params: { query: string; max_results?: number }): Promise { try { @@ -508,41 +508,41 @@ export async function handleWebSearch(params: { query: string; max_results?: num 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' }; - // 1. 百度搜索(国内首选) + // 1. Bing 搜索(首选) let results: Array<{ title: string; url: string; snippet: string }> = []; try { - const baiduUrl = `https://www.baidu.com/s?wd=${encodeURIComponent(query)}&rn=${maxResults}`; - const resp = await fetch(baiduUrl, { + const bingUrl = `https://www.bing.com/search?q=${encodeURIComponent(query)}&count=${maxResults}`; + const resp = await fetch(bingUrl, { headers: searchHeaders, }); if (resp.ok) { const html = await resp.text(); - results = parseBaiduResults(html, maxResults); + results = parseBingResults(html, maxResults); } } catch (e) { - sendLog('warn', `🔍 百度搜索失败`, (e as Error).message); + sendLog('warn', `🔍 Bing 搜索失败`, (e as Error).message); } - // 2. 百度无结果,尝试 Bing + // 2. Bing 无结果,尝试百度 if (results.length === 0) { try { - const bingUrl = `https://www.bing.com/search?q=${encodeURIComponent(query)}&count=${maxResults}`; - const resp = await fetch(bingUrl, { + const baiduUrl = `https://www.baidu.com/s?wd=${encodeURIComponent(query)}&rn=${maxResults}`; + const resp = await fetch(baiduUrl, { headers: searchHeaders, }); if (resp.ok) { const html = await resp.text(); - results = parseBingResults(html, maxResults); + results = parseBaiduResults(html, maxResults); } } catch (e) { - sendLog('warn', `🔍 Bing 搜索失败`, (e as Error).message); + sendLog('warn', `🔍 百度搜索失败`, (e as Error).message); } } - // 3. Bing 也失败,尝试 Google + // 3. 百度也失败,尝试 Google if (results.length === 0) { try { const googleUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}&num=${maxResults}`; diff --git a/src/renderer/components/settings-modal.ts b/src/renderer/components/settings-modal.ts index 374cfc7..19e736e 100644 --- a/src/renderer/components/settings-modal.ts +++ b/src/renderer/components/settings-modal.ts @@ -6,7 +6,7 @@ import { state, KEYS } from '../state/state.js'; import { debounce } from '../utils/utils.js'; import { encryptData, decryptData } from '../services/crypto.js'; import { setMemoryEnabled, setEmbeddingModel, getEmbeddingModel, isVectorMemoryEnabled, getMemoryCache } from '../services/memory-manager.js'; -import { logSetting, logInfo, logWarn, logError, logSuccess, getLogs } from '../services/log-service.js'; +import { logSetting, logInfo, logWarn, logError, logSuccess } from '../services/log-service.js'; import { checkConnection, updateConnectionInfo, updateRunningModels } from './header.js'; import { loadModels } from './model-bar.js'; import { showToast } from './toast.js'; @@ -84,9 +84,6 @@ export function initSettingsModal(): void { }, 500); document.querySelector('#inputMaxTurns')!.addEventListener('input', saveMaxTurns); - // ── v5.1.1 执行日志导出 ── - document.querySelector('#btnExportLog')!.addEventListener('click', exportExecutionLog); - // ── v4.2 人格模式切换 ── const PERSONALITY_TEMPERATURES: Record = { default: -1, // 使用用户设置 @@ -392,57 +389,6 @@ export function closeSettingsModal(): void { settingsModalEl.style.display = 'none'; } -/** 导出执行日志为 TXT 文件 */ -async function exportExecutionLog(): Promise { - const logs = getLogs(); - if (logs.length === 0) { - showToast('没有可导出的日志', 'warning'); - return; - } - - const formatTime = (ts: number): string => { - const d = new Date(ts); - return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`; - }; - - const lines = logs.map(e => { - let line = `[${formatTime(e.time)}] ${e.level.toUpperCase()} ${e.icon} ${e.message}`; - if (e.detail) line += `\n ${e.detail}`; - return line; - }); - - const content = `Metona Ollama Desktop — 执行日志\n导出时间: ${formatTime(Date.now())}\n日志条数: ${logs.length}\n${'═'.repeat(60)}\n\n${lines.join('\n')}`; - - try { - const bridge = (window as any).metonaDesktop; - if (bridge) { - const ts = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-'); - const filePath = await bridge.dialog.saveFile({ - defaultPath: `metona-log-${ts}.txt`, - filters: [{ name: '文本文件', extensions: ['txt'] }] - }); - if (!filePath) return; - const result = await bridge.fs.writeFile(filePath, content, 'utf-8'); - if (!result.success) { - showToast(`导出失败: ${result.error}`, 'error'); - return; - } - } else { - const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); - const a = document.createElement('a'); - a.href = URL.createObjectURL(blob); - a.download = `metona-log-${Date.now()}.txt`; - a.click(); - } - - showToast(`已导出 ${logs.length} 条日志`, 'success'); - logSuccess(`日志导出完成: ${logs.length} 条`); - } catch (err) { - showToast(`导出失败: ${(err as Error).message}`, 'error'); - logError('日志导出失败', (err as Error).message); - } -} - async function exportAllSessions(): Promise { const db = state.get(KEYS.DB); if (!db) return; diff --git a/src/renderer/services/log-service.ts b/src/renderer/services/log-service.ts index f5c06cc..1b01891 100644 --- a/src/renderer/services/log-service.ts +++ b/src/renderer/services/log-service.ts @@ -50,7 +50,9 @@ export function initLogPanel(): void { } catch { /* ignore */ } }); - // ── 关键修复:flush initLogPanel 前缓冲的日志 ── + // 导出日志按钮 + document.querySelector('#btnExportLog')?.addEventListener('click', exportLog); + panelInitialized = true; if (earlyBuffer.length > 0 && logBodyEl) { const frag = document.createDocumentFragment(); @@ -190,3 +192,43 @@ export function logInit(msg: string): void { addLog('info', `初始化: ${msg}`) export function getLogs(): LogEntry[] { return [...logs]; } + +/** 导出执行日志为 TXT 文件 */ +async function exportLog(): Promise { + if (logs.length === 0) { + return; + } + + const fmtTime = (ts: number): string => { + const d = new Date(ts); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`; + }; + + const lines = logs.map(e => { + let line = `[${fmtTime(e.time)}] ${e.level.toUpperCase()} ${e.icon} ${e.message}`; + if (e.detail) line += `\n ${e.detail}`; + return line; + }); + + const header = `Metona Ollama Desktop — 执行日志\n导出时间: ${fmtTime(Date.now())}\n日志条数: ${logs.length}\n${'═'.repeat(60)}\n\n`; + const content = header + lines.join('\n'); + + try { + const bridge = (window as any).metonaDesktop; + if (bridge) { + const ts = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-'); + const filePath = await bridge.dialog.saveFile({ + defaultPath: `metona-log-${ts}.txt`, + filters: [{ name: '文本文件', extensions: ['txt'] }] + }); + if (!filePath) return; + await bridge.fs.writeFile(filePath, content, 'utf-8'); + } else { + const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `metona-log-${Date.now()}.txt`; + a.click(); + } + } catch { /* ignore */ } +}