Refactor web search engine handling and add new file for engine options
This commit is contained in:
21
src/App.jsx
21
src/App.jsx
@@ -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))}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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
67
src/websearchEngines.js
Normal 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]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user