From 9a547849eb636e206ab4c6bf052ef4ae96a9d638 Mon Sep 17 00:00:00 2001 From: thzxx Date: Fri, 12 Jun 2026 12:55:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20SearXNG=20=E6=B7=BB=E5=8A=A0=20format?= =?UTF-8?q?=20=E5=8F=82=E6=95=B0=EF=BC=8C=E6=94=AF=E6=8C=81=20json/html=20?= =?UTF-8?q?=E4=B8=A4=E7=A7=8D=E6=A8=A1=E5=BC=8F=E3=80=82html=20=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E6=8F=90=E5=8F=96=E7=BD=91=E9=A1=B5=E7=BA=AF=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E4=BA=A4=E7=94=B1=20AI=20=E8=87=AA=E8=A1=8C=E5=88=86?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/tool-handlers-system.ts | 35 ++++++++++++++++++------ src/renderer/components/searxng-modal.ts | 5 ++++ src/renderer/index.html | 7 +++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/main/tool-handlers-system.ts b/src/main/tool-handlers-system.ts index afc3af0..d5dd3d0 100644 --- a/src/main/tool-handlers-system.ts +++ b/src/main/tool-handlers-system.ts @@ -456,10 +456,11 @@ async function handleWebSearchSearxng( const cfgTimeRange = getSetting('searxng_time_range', ''); const effectiveTimeRange = timeRange || cfgTimeRange; - const params = new URLSearchParams({ - q: query, - format: 'json', - }); + const format = getSetting('searxng_format', 'json'); + const isHtml = format === 'html'; + + const params = new URLSearchParams({ q: query }); + if (!isHtml) params.set('format', 'json'); if (engines) params.set('engines', engines); if (language) params.set('language', language); if (safesearch > 0) params.set('safesearch', String(safesearch)); @@ -468,18 +469,36 @@ async function handleWebSearchSearxng( const authKey = getSetting('searxng_auth_key', ''); const apiUrl = `${url.replace(/\/+$/, '')}/search?${params.toString()}`; - sendLog('info', `🔍 SearXNG 搜索`, `"${query}" → ${url}`); + sendLog('info', `🔍 SearXNG 搜索`, `${query} → ${url} (${format})`); try { const headers = buildFetchHeaders(apiUrl, false); if (authKey) headers['Authorization'] = `Bearer ${authKey}`; const resp = await fetchWithTimeout(apiUrl, HTTP_TIMEOUT, headers); if (!resp || !resp.ok) { - const fallbackUrl = encodeURIComponent(query); return { success: false, - error: `SearXNG 搜索失败 (HTTP ${resp?.status || 'timeout'})。请检查 SearXNG 实例是否运行正常:${url}。你也可以在设置中禁用 SearXNG 回退到内置四引擎搜索。`, - _searxng_fallback: `${url}/search?q=${fallbackUrl}` + error: `SearXNG 搜索失败 (HTTP ${resp?.status || 'timeout'})。请检查 SearXNG 实例是否运行正常:${url}。`, + _searxng_url: apiUrl + }; + } + + // ── HTML 模式:返回原始网页文本,让 AI 自行分析 ── + if (isHtml) { + const html = await resp.text(); + const text = htmlToText(html); + const truncated = maxResults > 0 && text.length > maxResults * 500; + const content = truncated ? text.slice(0, maxResults * 500) + '\n...(已截断)' : text; + sendLog('success', `🔍 SearXNG HTML 完成`, `${text.length} 字符`); + return { + success: true, + url: apiUrl, + content, + content_type: 'text/html', + length: content.length, + truncated, + format: 'html', + _hint: '这是 SearXNG 搜索结果页面的纯文本提取。请从中提取有用的搜索结果信息来回答用户问题。' }; } diff --git a/src/renderer/components/searxng-modal.ts b/src/renderer/components/searxng-modal.ts index 1e4fc5c..4bbe94f 100644 --- a/src/renderer/components/searxng-modal.ts +++ b/src/renderer/components/searxng-modal.ts @@ -17,6 +17,7 @@ const DEFAULTS = { time_range: '', max_results: 20, auth_key: '', + format: 'json', }; let modalEl: HTMLElement; @@ -35,6 +36,7 @@ async function saveConfig(db: ChatDB): Promise { await db.saveSetting('searxng_time_range', searxngConfig.time_range); await db.saveSetting('searxng_max_results', searxngConfig.max_results); await db.saveSetting('searxng_auth_key', searxngConfig.auth_key); + await db.saveSetting('searxng_format', searxngConfig.format); logSuccess('SearXNG 配置已保存'); } @@ -48,6 +50,7 @@ export async function loadSearxngConfig(db: ChatDB): Promise { searxngConfig.time_range = await db.getSetting('searxng_time_range', DEFAULTS.time_range); searxngConfig.max_results = await db.getSetting('searxng_max_results', DEFAULTS.max_results); searxngConfig.auth_key = await db.getSetting('searxng_auth_key', DEFAULTS.auth_key); + searxngConfig.format = await db.getSetting('searxng_format', DEFAULTS.format); logInfo('SearXNG 配置已加载', searxngConfig.enabled ? `已启用: ${searxngConfig.url}` : '未启用'); } @@ -60,6 +63,7 @@ function syncFormFromConfig(): void { (document.querySelector('#searxngTimeRange') as HTMLSelectElement).value = searxngConfig.time_range || ''; (document.querySelector('#searxngMaxResults') as HTMLInputElement).value = String(searxngConfig.max_results); (document.querySelector('#searxngAuthKey') as HTMLInputElement).value = searxngConfig.auth_key; + (document.querySelector('#searxngFormat') as HTMLSelectElement).value = searxngConfig.format; const toggle = document.querySelector('#toggleSearxngEnabled') as HTMLInputElement; toggle.checked = searxngConfig.enabled; updateEnabledUI(searxngConfig.enabled); @@ -132,6 +136,7 @@ export function initSearxngModal(): void { searxngConfig.time_range = (document.querySelector('#searxngTimeRange') as HTMLSelectElement).value; searxngConfig.max_results = parseInt((document.querySelector('#searxngMaxResults') as HTMLInputElement).value) || 20; searxngConfig.auth_key = (document.querySelector('#searxngAuthKey') as HTMLInputElement).value.trim(); + searxngConfig.format = (document.querySelector('#searxngFormat') as HTMLSelectElement).value; const db = state.get(KEYS.DB); if (db) await saveConfig(db); updateEnabledUI(searxngConfig.enabled); diff --git a/src/renderer/index.html b/src/renderer/index.html index 5c30a21..63c46dc 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -776,6 +776,13 @@ +
+ + +