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 && ( {