fix: 日志导出移至日志顶栏, 搜索引擎Bing优先
This commit is contained in:
+19
-19
@@ -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<ToolResult> {
|
||||
try {
|
||||
@@ -508,25 +508,9 @@ 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, {
|
||||
headers: searchHeaders,
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
const html = await resp.text();
|
||||
results = parseBaiduResults(html, maxResults);
|
||||
}
|
||||
} catch (e) {
|
||||
sendLog('warn', `🔍 百度搜索失败`, (e as Error).message);
|
||||
}
|
||||
|
||||
// 2. 百度无结果,尝试 Bing
|
||||
if (results.length === 0) {
|
||||
try {
|
||||
const bingUrl = `https://www.bing.com/search?q=${encodeURIComponent(query)}&count=${maxResults}`;
|
||||
const resp = await fetch(bingUrl, {
|
||||
@@ -540,9 +524,25 @@ export async function handleWebSearch(params: { query: string; max_results?: num
|
||||
} catch (e) {
|
||||
sendLog('warn', `🔍 Bing 搜索失败`, (e as Error).message);
|
||||
}
|
||||
|
||||
// 2. Bing 无结果,尝试百度
|
||||
if (results.length === 0) {
|
||||
try {
|
||||
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 = parseBaiduResults(html, maxResults);
|
||||
}
|
||||
} catch (e) {
|
||||
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}`;
|
||||
|
||||
@@ -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<string, number> = {
|
||||
default: -1, // 使用用户设置
|
||||
@@ -392,57 +389,6 @@ export function closeSettingsModal(): void {
|
||||
settingsModalEl.style.display = 'none';
|
||||
}
|
||||
|
||||
/** 导出执行日志为 TXT 文件 */
|
||||
async function exportExecutionLog(): Promise<void> {
|
||||
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<void> {
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
if (!db) return;
|
||||
|
||||
@@ -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<void> {
|
||||
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 */ }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user