Refactor web search engine handling and add new file for engine options

This commit is contained in:
2026-03-20 12:55:56 +01:00
parent d86d581021
commit 645190405f
3 changed files with 88 additions and 33 deletions

View File

@@ -8,6 +8,10 @@ import LibraryManager from './LibraryManager'
import WebsearchSettings from './WebsearchSettings'
import { markdownToHTML } from './markdown';
import { applyColorScheme } from './colorSchemes'
import {
loadStoredWebsearchEngines,
normalizeWebsearchEngines,
} from './websearchEngines'
// Extract <think> or <thinking> block (first occurrence) and return { think, answer }
function splitThinkBlocks(text) {
if (!text) return { think: null, answer: '' };
@@ -180,20 +184,19 @@ export default function App() {
const [startupTaskMessage, setStartupTaskMessage] = useState('');
const [startupTaskBusy, setStartupTaskBusy] = useState(false);
const [searxUrl, setSearxUrl] = useState(() => migrateLegacySearxUrl(localStorage.getItem(WEBSEARCH_URL_KEY)));
const [searxEngines, setSearxEngines] = useState(() => {
try {
const raw = localStorage.getItem(WEBSEARCH_ENGINES_KEY);
if (raw) return JSON.parse(raw);
} catch {}
return ["duckduckgo","bing","wikipedia","github","stack_overflow"];
});
const [searxEngines, setSearxEngines] = useState(() =>
loadStoredWebsearchEngines(localStorage.getItem(WEBSEARCH_ENGINES_KEY))
);
useEffect(() => {
localStorage.setItem(WEBSEARCH_URL_KEY, searxUrl || '');
}, [searxUrl]);
useEffect(() => {
try {
localStorage.setItem(WEBSEARCH_ENGINES_KEY, JSON.stringify(searxEngines || []));
localStorage.setItem(
WEBSEARCH_ENGINES_KEY,
JSON.stringify(normalizeWebsearchEngines(searxEngines))
);
} catch {}
}, [searxEngines]);
const [webSearchEnabled, setWebSearchEnabled] = useState(false);
@@ -2148,7 +2151,7 @@ async function createNewChat() {
searxUrl={searxUrl}
setSearxUrl={setSearxUrl}
engines={searxEngines}
setEngines={setSearxEngines}
setEngines={(next) => setSearxEngines(normalizeWebsearchEngines(next))}
/>
)}
</>

View File

@@ -1,5 +1,6 @@
// src/WebsearchSettings.jsx
import React, { useEffect, useMemo, useState } from 'react';
import React from 'react';
import { WEBSEARCH_ENGINE_OPTIONS, normalizeWebsearchEngines } from './websearchEngines'
export default function WebsearchSettings({
searxUrl,
@@ -7,27 +8,11 @@ export default function WebsearchSettings({
engines,
setEngines,
}) {
const KNOWN_ENGINES = useMemo(
() => ["google","bing","yahoo","duckduckgo","brave","github","stackoverflow","reddit","arxiv"],
[]
);
const [custom, setCustom] = useState("");
const toggleEngine = (name) => {
const set = new Set(engines || []);
if (set.has(name)) set.delete(name); else set.add(name);
setEngines(Array.from(set));
};
const addCustom = () => {
const name = custom.trim();
if (!name) return;
const set = new Set(engines || []);
set.add(name);
setEngines(Array.from(set));
setCustom("");
};
setEngines(normalizeWebsearchEngines(Array.from(set)));
}
return (
<div className="settings-content-panel">
@@ -45,14 +30,14 @@ return (
<div className="setting-section">
<h3>Search Engines</h3>
<div className="engine-grid">
{KNOWN_ENGINES.map(name => (
<label key={name} className="engine-row">
{WEBSEARCH_ENGINE_OPTIONS.map(({ value, label }) => (
<label key={value} className="engine-row">
<input
type="checkbox"
checked={Array.isArray(engines) ? engines.includes(name) : false}
onChange={() => toggleEngine(name)}
checked={Array.isArray(engines) ? engines.includes(value) : false}
onChange={() => toggleEngine(value)}
/>
<span>{name}</span>
<span>{label}</span>
</label>
))}
</div>

67
src/websearchEngines.js Normal file
View File

@@ -0,0 +1,67 @@
export const WEBSEARCH_ENGINE_OPTIONS = [
{ value: 'google', label: 'Google' },
{ value: 'bing', label: 'Bing' },
{ value: 'yahoo', label: 'Yahoo' },
{ value: 'duckduckgo', label: 'DuckDuckGo' },
{ value: 'brave', label: 'Brave' },
{ value: 'startpage', label: 'Startpage' },
{ value: 'wikipedia', label: 'Wikipedia' },
{ value: 'github', label: 'GitHub' },
{ value: 'stack_overflow', label: 'Stack Overflow' },
{ value: 'reddit', label: 'Reddit' },
{ value: 'arxiv', label: 'arXiv' },
]
export const DEFAULT_WEBSEARCH_ENGINES = [
'google',
'bing',
'duckduckgo',
'brave',
'startpage',
]
const WEBSEARCH_ENGINE_ALIASES = {
stackoverflow: 'stack_overflow',
}
const ENGINE_ORDER = new Map(
WEBSEARCH_ENGINE_OPTIONS.map((option, index) => [option.value, index])
)
export function normalizeWebsearchEngineId(value) {
if (typeof value !== 'string') return null
const trimmed = value.trim().toLowerCase()
if (!trimmed) return null
const canonical = WEBSEARCH_ENGINE_ALIASES[trimmed] ?? trimmed
return ENGINE_ORDER.has(canonical) ? canonical : null
}
export function normalizeWebsearchEngines(value) {
if (!Array.isArray(value)) return []
const seen = new Set()
const normalized = []
for (const entry of value) {
const canonical = normalizeWebsearchEngineId(entry)
if (!canonical || seen.has(canonical)) continue
seen.add(canonical)
normalized.push(canonical)
}
normalized.sort((left, right) => ENGINE_ORDER.get(left) - ENGINE_ORDER.get(right))
return normalized
}
export function loadStoredWebsearchEngines(rawValue) {
if (typeof rawValue !== 'string') return [...DEFAULT_WEBSEARCH_ENGINES]
try {
const parsed = JSON.parse(rawValue)
return normalizeWebsearchEngines(parsed)
} catch {
return [...DEFAULT_WEBSEARCH_ENGINES]
}
}