From 4c911240df0036389454f1ded655fa8806cf72f5 Mon Sep 17 00:00:00 2001
From: Steven Enamakel <31011319+senamakel@users.noreply.github.com>
Date: Fri, 29 May 2026 19:41:01 -0700
Subject: [PATCH] feat(search): add registry-backed search module (#2999)
---
.../settings/panels/SearchPanel.test.tsx | 59 ++++++++
.../settings/panels/SearchPanel.tsx | 10 +-
app/src/lib/i18n/chunks/ar-1.ts | 3 +
app/src/lib/i18n/chunks/bn-1.ts | 3 +
app/src/lib/i18n/chunks/de-1.ts | 3 +
app/src/lib/i18n/chunks/en-1.ts | 5 +-
app/src/lib/i18n/chunks/es-1.ts | 3 +
app/src/lib/i18n/chunks/fr-1.ts | 3 +
app/src/lib/i18n/chunks/hi-1.ts | 3 +
app/src/lib/i18n/chunks/id-1.ts | 3 +
app/src/lib/i18n/chunks/it-1.ts | 3 +
app/src/lib/i18n/chunks/ko-1.ts | 3 +
app/src/lib/i18n/chunks/pt-1.ts | 3 +
app/src/lib/i18n/chunks/ru-1.ts | 3 +
app/src/lib/i18n/chunks/zh-CN-1.ts | 3 +
app/src/lib/i18n/en.ts | 5 +-
app/src/utils/tauriCommands/config.ts | 2 +-
app/test/e2e/specs/settings-search.spec.ts | 87 +++++++++++
src/openhuman/config/mod.rs | 4 +-
src/openhuman/config/ops.rs | 7 +-
src/openhuman/config/ops_tests.rs | 40 +++++
src/openhuman/config/schema/load.rs | 5 +-
src/openhuman/config/schema/mod.rs | 4 +-
src/openhuman/config/schema/tools.rs | 34 +++--
src/openhuman/integrations/README.md | 112 ++++++--------
src/openhuman/integrations/tools.rs | 20 ---
src/openhuman/mod.rs | 1 +
src/openhuman/search/README.md | 22 +++
src/openhuman/search/engines/brave.rs | 31 ++++
src/openhuman/search/engines/disabled.rs | 8 +
src/openhuman/search/engines/managed.rs | 16 ++
src/openhuman/search/engines/mod.rs | 5 +
src/openhuman/search/engines/parallel.rs | 47 ++++++
src/openhuman/search/engines/querit.rs | 25 ++++
src/openhuman/search/mod.rs | 13 ++
src/openhuman/search/registry.rs | 111 ++++++++++++++
.../{integrations => search}/tools/brave.rs | 0
src/openhuman/search/tools/mod.rs | 23 +++
.../tools/parallel.rs | 2 +-
.../tools/parallel_tests.rs | 0
.../{integrations => search}/tools/querit.rs | 0
.../{integrations => search}/tools/searxng.rs | 0
.../{integrations => search}/tools/seltz.rs | 0
.../tools/tinyfish.rs | 2 +-
.../tools/tinyfish_tests.rs | 0
.../network => search/tools}/web_search.rs | 2 +-
src/openhuman/tools/README.md | 9 +-
src/openhuman/tools/impl/network/mod.rs | 2 -
src/openhuman/tools/mod.rs | 1 +
src/openhuman/tools/ops.rs | 137 +-----------------
src/openhuman/tools/ops_tests.rs | 45 ++++++
src/openhuman/tools/schemas.rs | 12 +-
52 files changed, 684 insertions(+), 260 deletions(-)
create mode 100644 app/test/e2e/specs/settings-search.spec.ts
create mode 100644 src/openhuman/search/README.md
create mode 100644 src/openhuman/search/engines/brave.rs
create mode 100644 src/openhuman/search/engines/disabled.rs
create mode 100644 src/openhuman/search/engines/managed.rs
create mode 100644 src/openhuman/search/engines/mod.rs
create mode 100644 src/openhuman/search/engines/parallel.rs
create mode 100644 src/openhuman/search/engines/querit.rs
create mode 100644 src/openhuman/search/mod.rs
create mode 100644 src/openhuman/search/registry.rs
rename src/openhuman/{integrations => search}/tools/brave.rs (100%)
create mode 100644 src/openhuman/search/tools/mod.rs
rename src/openhuman/{integrations => search}/tools/parallel.rs (99%)
rename src/openhuman/{integrations => search}/tools/parallel_tests.rs (100%)
rename src/openhuman/{integrations => search}/tools/querit.rs (100%)
rename src/openhuman/{integrations => search}/tools/searxng.rs (100%)
rename src/openhuman/{integrations => search}/tools/seltz.rs (100%)
rename src/openhuman/{integrations => search}/tools/tinyfish.rs (99%)
rename src/openhuman/{integrations => search}/tools/tinyfish_tests.rs (100%)
rename src/openhuman/{tools/impl/network => search/tools}/web_search.rs (99%)
diff --git a/app/src/components/settings/panels/SearchPanel.test.tsx b/app/src/components/settings/panels/SearchPanel.test.tsx
index 1b833cb5d..0ec5066a9 100644
--- a/app/src/components/settings/panels/SearchPanel.test.tsx
+++ b/app/src/components/settings/panels/SearchPanel.test.tsx
@@ -77,6 +77,29 @@ describe('SearchPanel — unified web-access modes', () => {
expect(radio(ALLOW_ALL)).toHaveAttribute('aria-checked', 'false');
});
+ test('selecting Disabled persists the disabled engine', async () => {
+ renderWithProviders();
+ const disabled = await screen.findByTestId('search-engine-disabled');
+
+ fireEvent.click(disabled);
+
+ await waitFor(() =>
+ expect(hoisted.updateSearchSettings).toHaveBeenCalledWith({ engine: 'disabled' })
+ );
+ });
+
+ test('disabled settings start with Disabled selected and no needs-key badge', async () => {
+ hoisted.getSearchSettings.mockResolvedValue({
+ result: settings({ engine: 'disabled', effective_engine: 'disabled' }),
+ });
+ renderWithProviders();
+
+ const disabled = await screen.findByTestId('search-engine-disabled');
+
+ expect(disabled).toHaveAttribute('aria-checked', 'true');
+ expect(within(disabled).queryByText('settings.search.statusNeedsKey')).toBeNull();
+ });
+
test('selecting "Allow all" persists allow_all: true and hides the editor', async () => {
renderWithProviders();
await screen.findByPlaceholderText(PLACEHOLDER);
@@ -204,6 +227,42 @@ describe('SearchPanel — unified web-access modes', () => {
expect(braveInput.value).toBe('');
});
+ test('Parallel and Brave key editors can reveal and clear stored keys', async () => {
+ hoisted.getSearchSettings.mockResolvedValue({
+ result: settings({ parallel_configured: true, brave_configured: true }),
+ });
+ renderWithProviders();
+ await screen.findAllByPlaceholderText('settings.search.placeholderStored');
+
+ const parallel = keyEditor('settings.search.parallelKeyLabel');
+ const parallelInput = parallel.getByPlaceholderText(
+ 'settings.search.placeholderStored'
+ ) as HTMLInputElement;
+ expect(parallelInput.type).toBe('password');
+
+ fireEvent.click(parallel.getByText('settings.search.show'));
+ expect(parallelInput.type).toBe('text');
+ fireEvent.click(parallel.getByText('settings.search.clear'));
+
+ await waitFor(() =>
+ expect(hoisted.updateSearchSettings).toHaveBeenCalledWith({ parallel_api_key: '' })
+ );
+
+ const brave = keyEditor('settings.search.braveKeyLabel');
+ const braveInput = brave.getByPlaceholderText(
+ 'settings.search.placeholderStored'
+ ) as HTMLInputElement;
+ expect(braveInput.type).toBe('password');
+
+ fireEvent.click(brave.getByText('settings.search.show'));
+ expect(braveInput.type).toBe('text');
+ fireEvent.click(brave.getByText('settings.search.clear'));
+
+ await waitFor(() =>
+ expect(hoisted.updateSearchSettings).toHaveBeenCalledWith({ brave_api_key: '' })
+ );
+ });
+
test('Querit key editor can reveal, save, and clear the stored API key', async () => {
hoisted.getSearchSettings.mockResolvedValue({ result: settings({ querit_configured: true }) });
renderWithProviders();
diff --git a/app/src/components/settings/panels/SearchPanel.tsx b/app/src/components/settings/panels/SearchPanel.tsx
index f338cc2c7..a5c30ddd3 100644
--- a/app/src/components/settings/panels/SearchPanel.tsx
+++ b/app/src/components/settings/panels/SearchPanel.tsx
@@ -77,6 +77,12 @@ const SearchPanel = ({ embedded = false }: { embedded?: boolean }) => {
const initializedRef = useRef(false);
const ENGINES: EngineOption[] = [
+ {
+ id: 'disabled',
+ label: t('settings.search.engineDisabledLabel'),
+ description: t('settings.search.engineDisabledDesc'),
+ requiresKey: false,
+ },
{
id: 'managed',
label: t('settings.search.engineManagedLabel'),
@@ -206,6 +212,7 @@ const SearchPanel = ({ embedded = false }: { embedded?: boolean }) => {
const isConfigured = (engine: SearchEngineId): boolean => {
if (!settings) return false;
+ if (engine === 'disabled') return true;
if (engine === 'managed') return true;
if (engine === 'parallel') return settings.parallel_configured;
if (engine === 'brave') return settings.brave_configured;
@@ -214,7 +221,7 @@ const SearchPanel = ({ embedded = false }: { embedded?: boolean }) => {
};
return (
-
+
{!embedded && (
{