feat: SearXNG 添加 format 参数,支持 json/html 两种模式。html 模式提取网页纯文本交由 AI 自行分析
This commit is contained in:
@@ -456,10 +456,11 @@ async function handleWebSearchSearxng(
|
|||||||
const cfgTimeRange = getSetting<string>('searxng_time_range', '');
|
const cfgTimeRange = getSetting<string>('searxng_time_range', '');
|
||||||
const effectiveTimeRange = timeRange || cfgTimeRange;
|
const effectiveTimeRange = timeRange || cfgTimeRange;
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const format = getSetting<string>('searxng_format', 'json');
|
||||||
q: query,
|
const isHtml = format === 'html';
|
||||||
format: 'json',
|
|
||||||
});
|
const params = new URLSearchParams({ q: query });
|
||||||
|
if (!isHtml) params.set('format', 'json');
|
||||||
if (engines) params.set('engines', engines);
|
if (engines) params.set('engines', engines);
|
||||||
if (language) params.set('language', language);
|
if (language) params.set('language', language);
|
||||||
if (safesearch > 0) params.set('safesearch', String(safesearch));
|
if (safesearch > 0) params.set('safesearch', String(safesearch));
|
||||||
@@ -468,18 +469,36 @@ async function handleWebSearchSearxng(
|
|||||||
const authKey = getSetting<string>('searxng_auth_key', '');
|
const authKey = getSetting<string>('searxng_auth_key', '');
|
||||||
|
|
||||||
const apiUrl = `${url.replace(/\/+$/, '')}/search?${params.toString()}`;
|
const apiUrl = `${url.replace(/\/+$/, '')}/search?${params.toString()}`;
|
||||||
sendLog('info', `🔍 SearXNG 搜索`, `"${query}" → ${url}`);
|
sendLog('info', `🔍 SearXNG 搜索`, `${query} → ${url} (${format})`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const headers = buildFetchHeaders(apiUrl, false);
|
const headers = buildFetchHeaders(apiUrl, false);
|
||||||
if (authKey) headers['Authorization'] = `Bearer ${authKey}`;
|
if (authKey) headers['Authorization'] = `Bearer ${authKey}`;
|
||||||
const resp = await fetchWithTimeout(apiUrl, HTTP_TIMEOUT, headers);
|
const resp = await fetchWithTimeout(apiUrl, HTTP_TIMEOUT, headers);
|
||||||
if (!resp || !resp.ok) {
|
if (!resp || !resp.ok) {
|
||||||
const fallbackUrl = encodeURIComponent(query);
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: `SearXNG 搜索失败 (HTTP ${resp?.status || 'timeout'})。请检查 SearXNG 实例是否运行正常:${url}。你也可以在设置中禁用 SearXNG 回退到内置四引擎搜索。`,
|
error: `SearXNG 搜索失败 (HTTP ${resp?.status || 'timeout'})。请检查 SearXNG 实例是否运行正常:${url}。`,
|
||||||
_searxng_fallback: `${url}/search?q=${fallbackUrl}`
|
_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 搜索结果页面的纯文本提取。请从中提取有用的搜索结果信息来回答用户问题。'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const DEFAULTS = {
|
|||||||
time_range: '',
|
time_range: '',
|
||||||
max_results: 20,
|
max_results: 20,
|
||||||
auth_key: '',
|
auth_key: '',
|
||||||
|
format: 'json',
|
||||||
};
|
};
|
||||||
|
|
||||||
let modalEl: HTMLElement;
|
let modalEl: HTMLElement;
|
||||||
@@ -35,6 +36,7 @@ async function saveConfig(db: ChatDB): Promise<void> {
|
|||||||
await db.saveSetting('searxng_time_range', searxngConfig.time_range);
|
await db.saveSetting('searxng_time_range', searxngConfig.time_range);
|
||||||
await db.saveSetting('searxng_max_results', searxngConfig.max_results);
|
await db.saveSetting('searxng_max_results', searxngConfig.max_results);
|
||||||
await db.saveSetting('searxng_auth_key', searxngConfig.auth_key);
|
await db.saveSetting('searxng_auth_key', searxngConfig.auth_key);
|
||||||
|
await db.saveSetting('searxng_format', searxngConfig.format);
|
||||||
logSuccess('SearXNG 配置已保存');
|
logSuccess('SearXNG 配置已保存');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ export async function loadSearxngConfig(db: ChatDB): Promise<void> {
|
|||||||
searxngConfig.time_range = await db.getSetting('searxng_time_range', DEFAULTS.time_range);
|
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.max_results = await db.getSetting('searxng_max_results', DEFAULTS.max_results);
|
||||||
searxngConfig.auth_key = await db.getSetting('searxng_auth_key', DEFAULTS.auth_key);
|
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}` : '未启用');
|
logInfo('SearXNG 配置已加载', searxngConfig.enabled ? `已启用: ${searxngConfig.url}` : '未启用');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +63,7 @@ function syncFormFromConfig(): void {
|
|||||||
(document.querySelector('#searxngTimeRange') as HTMLSelectElement).value = searxngConfig.time_range || '';
|
(document.querySelector('#searxngTimeRange') as HTMLSelectElement).value = searxngConfig.time_range || '';
|
||||||
(document.querySelector('#searxngMaxResults') as HTMLInputElement).value = String(searxngConfig.max_results);
|
(document.querySelector('#searxngMaxResults') as HTMLInputElement).value = String(searxngConfig.max_results);
|
||||||
(document.querySelector('#searxngAuthKey') as HTMLInputElement).value = searxngConfig.auth_key;
|
(document.querySelector('#searxngAuthKey') as HTMLInputElement).value = searxngConfig.auth_key;
|
||||||
|
(document.querySelector('#searxngFormat') as HTMLSelectElement).value = searxngConfig.format;
|
||||||
const toggle = document.querySelector('#toggleSearxngEnabled') as HTMLInputElement;
|
const toggle = document.querySelector('#toggleSearxngEnabled') as HTMLInputElement;
|
||||||
toggle.checked = searxngConfig.enabled;
|
toggle.checked = searxngConfig.enabled;
|
||||||
updateEnabledUI(searxngConfig.enabled);
|
updateEnabledUI(searxngConfig.enabled);
|
||||||
@@ -132,6 +136,7 @@ export function initSearxngModal(): void {
|
|||||||
searxngConfig.time_range = (document.querySelector('#searxngTimeRange') as HTMLSelectElement).value;
|
searxngConfig.time_range = (document.querySelector('#searxngTimeRange') as HTMLSelectElement).value;
|
||||||
searxngConfig.max_results = parseInt((document.querySelector('#searxngMaxResults') as HTMLInputElement).value) || 20;
|
searxngConfig.max_results = parseInt((document.querySelector('#searxngMaxResults') as HTMLInputElement).value) || 20;
|
||||||
searxngConfig.auth_key = (document.querySelector('#searxngAuthKey') as HTMLInputElement).value.trim();
|
searxngConfig.auth_key = (document.querySelector('#searxngAuthKey') as HTMLInputElement).value.trim();
|
||||||
|
searxngConfig.format = (document.querySelector('#searxngFormat') as HTMLSelectElement).value;
|
||||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||||
if (db) await saveConfig(db);
|
if (db) await saveConfig(db);
|
||||||
updateEnabledUI(searxngConfig.enabled);
|
updateEnabledUI(searxngConfig.enabled);
|
||||||
|
|||||||
@@ -776,6 +776,13 @@
|
|||||||
<label class="setting-label">单次结果数</label>
|
<label class="setting-label">单次结果数</label>
|
||||||
<input class="setting-input" id="searxngMaxResults" type="number" min="1" max="50" step="1" value="20" style="margin-bottom:0;">
|
<input class="setting-input" id="searxngMaxResults" type="number" min="1" max="50" step="1" value="20" style="margin-bottom:0;">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-group">
|
||||||
|
<label class="setting-label">返回格式</label>
|
||||||
|
<select class="setting-input" id="searxngFormat" style="margin-bottom:0;">
|
||||||
|
<option value="json">JSON(结构化解析)</option>
|
||||||
|
<option value="html">HTML(原始网页,AI 自行分析)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-group">
|
<div class="setting-group">
|
||||||
|
|||||||
Reference in New Issue
Block a user