/** * SearXNGSettings — SearXNG 元搜索配置 Tab * * 从 SettingsModal.tsx 提取(v0.4.1 拆分)。 * 功能:SearXNG 实例配置(12 项)+ 连接测试。 */ import { useState } from 'react'; import { Button, TextField, Select, MenuItem, Stack, Typography, Divider, Chip, Switch, Alert, Box, FormControlLabel, InputLabel, FormControl, IconButton, CircularProgress, } from '@mui/material'; import { Eye, EyeOff } from 'lucide-react'; import { useConfig } from './useConfig'; export function SearXNGSettings() { // ===== 12 项配置(useConfig 实时持久化) ===== const [enabled, setEnabled] = useConfig('searxng.enabled', false); const [url, setUrl] = useConfig('searxng.url', ''); const [engines, setEngines] = useConfig('searxng.engines', ''); const [language, setLanguage] = useConfig('searxng.language', 'zh-CN'); const [safesearch, setSafesearch] = useConfig('searxng.safesearch', 1); const [timeRange, setTimeRange] = useConfig('searxng.time_range', ''); const [maxResults, setMaxResults] = useConfig('searxng.max_results', 0); const [authKey, setAuthKey] = useConfig('searxng.auth_key', ''); const [authType, setAuthType] = useConfig('searxng.auth_type', 'bearer'); const [format, setFormat] = useConfig('searxng.format', 'json'); const [fetchCount, setFetchCount] = useConfig('searxng.fetch_count', 0); const [fetchMode, setFetchMode] = useConfig('searxng.fetch_mode', 'sequential'); const [showKey, setShowKey] = useState(false); const [testing, setTesting] = useState(false); const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null); const urlError = !!url && !/^https?:\/\//.test(url); const handleTest = async () => { if (!url.trim() || urlError) return; setTesting(true); setTestResult(null); try { const result = await window.metona?.searxng?.testConnection(url.trim(), authKey, authType); if (result?.success) { setTestResult({ success: true, message: `连接成功(${result.latencyMs}ms)` }); } else { setTestResult({ success: false, message: result?.error || `连接失败(HTTP ${result?.statusCode})`, }); } } catch (e) { setTestResult({ success: false, message: (e as Error).message }); } setTesting(false); }; return ( {/* 标题 + 状态徽章 */} SearXNG 元搜索 SearXNG 是开源元搜索引擎,支持 70+ 搜索引擎聚合。启用后替代内置四引擎搜索通道,未启用时回退到 Bing + 百度 + 搜狗 + 360 搜索。 {/* 启用开关 */} setEnabled(e.target.checked)} size="small" /> } label={启用 SearXNG} /> {/* API 地址 + 连接测试 */} setUrl(e.target.value)} placeholder="如 https://searxng.example.com" error={urlError} helperText={ urlError ? '需以 http:// 或 https:// 开头' : '实例根地址(不含 /search 路径)' } sx={{ flex: 1 }} /> {/* 测试结果 */} {testResult && ( {testResult.message} )} {/* 搜索引擎 */} setEngines(e.target.value)} placeholder="如 google,bing,duckduckgo(留空使用实例默认)" /> {/* 语言 + 安全搜索 */} 语言 安全搜索 {/* 时间范围 + 返回格式 */} 时间范围 返回格式 {/* 最大结果数 + 自动抓取条数 */} setMaxResults(Number(e.target.value))} placeholder="0 表示使用默认" slotProps={{ htmlInput: { min: 0, max: 50 } }} /> setFetchCount(Number(e.target.value))} placeholder="0 表示由 AI 决定" slotProps={{ htmlInput: { min: 0, max: 8 } }} /> {/* 抓取类型 */} 抓取类型 {/* 认证设置 */} 认证设置 认证类型 setAuthKey(e.target.value)} placeholder={authType === 'bearer' ? '访问令牌原值' : 'username:password'} slotProps={{ input: { endAdornment: ( setShowKey(!showKey)}> {showKey ? : } ), }, }} /> {authType === 'bearer' ? 'Bearer: 直接填写令牌原值,原样透传到 Authorization 头。建议配合 HTTPS 使用。' : 'Basic: 填写 username:password 明文串,系统自动 Base64 编码。必须配合 HTTPS 使用。'} ); }