diff --git a/src/lib/server/websearch/runWebSearch.ts b/src/lib/server/websearch/runWebSearch.ts index 39f203b1a38..94725355184 100644 --- a/src/lib/server/websearch/runWebSearch.ts +++ b/src/lib/server/websearch/runWebSearch.ts @@ -7,6 +7,7 @@ import type { Assistant } from "$lib/types/Assistant"; import type { MessageWebSearchUpdate } from "$lib/types/MessageUpdate"; import { search } from "./search/search"; +import { WebSearchProvider } from "$lib/types/WebSearch"; import { scrape } from "./scrape/scrape"; import { findContextSources } from "./embed/embed"; import { removeParents } from "./markdown/tree"; @@ -27,7 +28,8 @@ export async function* runWebSearch( conv: Conversation, messages: Message[], ragSettings?: Assistant["rag"], - query?: string + query?: string, + provider?: WebSearchProvider ): AsyncGenerator { const prompt = messages[messages.length - 1].content; const createdAt = new Date(); @@ -43,7 +45,7 @@ export async function* runWebSearch( } // Search the web - const { searchQuery, pages } = yield* search(messages, ragSettings, query); + const { searchQuery, pages } = yield* search(messages, ragSettings, query, provider); if (pages.length === 0) throw Error("No results found for this search query"); // Scrape pages diff --git a/src/lib/server/websearch/search/endpoints.ts b/src/lib/server/websearch/search/endpoints.ts index 76bf2d746b2..c9da559f411 100644 --- a/src/lib/server/websearch/search/endpoints.ts +++ b/src/lib/server/websearch/search/endpoints.ts @@ -9,6 +9,19 @@ import searchSearxng from "./endpoints/searxng"; import searchSearchApi from "./endpoints/searchApi"; import searchBing from "./endpoints/bing"; +const providerMap: Record Promise> = { + [WebSearchProvider.GOOGLE]: searchSerper, + [WebSearchProvider.SERPER]: searchSerper, + [WebSearchProvider.BING]: searchBing, + [WebSearchProvider.DUCKDUCKGO]: searchSearxng, + [WebSearchProvider.YOU]: searchYouApi, + [WebSearchProvider.SEARXNG]: searchSearxng, + [WebSearchProvider.SERPAPI]: searchSerpApi, + [WebSearchProvider.SERPSTACK]: searchSerpStack, + [WebSearchProvider.SEARCHAPI]: searchSearchApi, + [WebSearchProvider.LOCAL]: searchWebLocal, +}; + export function getWebSearchProvider() { if (config.YDC_API_KEY) return WebSearchProvider.YOU; if (config.SEARXNG_QUERY_URL) return WebSearchProvider.SEARXNG; @@ -17,7 +30,15 @@ export function getWebSearchProvider() { } /** Searches the web using the first available provider, based on the env */ -export async function searchWeb(query: string): Promise { +export async function searchWeb( + query: string, + provider?: WebSearchProvider +): Promise { + if (provider) { + const fn = providerMap[provider]; + if (!fn) throw new Error(`Provider ${provider} not found`); + return fn(query); + } if (config.USE_LOCAL_WEBSEARCH) return searchWebLocal(query); if (config.SEARXNG_QUERY_URL) return searchSearxng(query); if (config.SERPER_API_KEY) return searchSerper(query); diff --git a/src/lib/stores/settings.ts b/src/lib/stores/settings.ts index c8e9615552e..16bd691bb66 100644 --- a/src/lib/stores/settings.ts +++ b/src/lib/stores/settings.ts @@ -5,6 +5,7 @@ import { UrlDependency } from "$lib/types/UrlDependency"; import type { ObjectId } from "mongodb"; import { getContext, setContext } from "svelte"; import { type Writable, writable, get } from "svelte/store"; +import { WebSearchProvider } from "$lib/types/WebSearch"; type SettingsStore = { shareConversationsWithModelAuthors: boolean; @@ -18,6 +19,7 @@ type SettingsStore = { tools?: Array; disableStream: boolean; directPaste: boolean; + preferredWebSearchEngine: WebSearchProvider; }; type SettingsStoreWritable = Writable & { @@ -31,6 +33,15 @@ export function useSettingsStore() { export function createSettingsStore(initialValue: Omit) { const baseStore = writable({ ...initialValue, recentlySaved: false }); + if (browser && !("preferredWebSearchEngine" in initialValue)) { + baseStore.update((s) => ({ + ...s, + preferredWebSearchEngine: + (localStorage.getItem("preferredWebSearchEngine") as WebSearchProvider) ?? + WebSearchProvider.GOOGLE, + })); + } + let timeoutId: NodeJS.Timeout; async function setSettings(settings: Partial) { diff --git a/src/lib/types/Settings.ts b/src/lib/types/Settings.ts index b66b0c8023b..bfa8a923aea 100644 --- a/src/lib/types/Settings.ts +++ b/src/lib/types/Settings.ts @@ -24,6 +24,7 @@ export interface Settings extends Timestamps { tools?: string[]; disableStream: boolean; directPaste: boolean; + preferredSearchEngine?: import("./WebSearch").WebSearchProvider; } export type SettingsEditable = Omit; diff --git a/src/lib/types/WebSearch.ts b/src/lib/types/WebSearch.ts index 7fddc3a99ae..3d89f57377f 100644 --- a/src/lib/types/WebSearch.ts +++ b/src/lib/types/WebSearch.ts @@ -46,4 +46,6 @@ export enum WebSearchProvider { YOU = "You.com", SEARXNG = "SearXNG", BING = "Bing", + SEPRER = "serper", + DUCKDUCKGO = "DuckDuckGo", } diff --git a/src/routes/settings/(nav)/+page.svelte b/src/routes/settings/(nav)/+page.svelte index e69de29bb2d..d8313a9f4e2 100644 --- a/src/routes/settings/(nav)/+page.svelte +++ b/src/routes/settings/(nav)/+page.svelte @@ -0,0 +1,81 @@ + + + + + +
+

Search Engine

+ + {#if savedFlash} + + {/if} +
+ +

+ + Choosing a provider changes which external search API HuggingChat calls + when the Web Search tool is triggered. +

+ + + + +
+ {#each data.websearchProviders as provider} + + {/each} +
diff --git a/src/routes/settings/+layout.server.ts b/src/routes/settings/+layout.server.ts index 17119bc8c8c..eb64110065e 100644 --- a/src/routes/settings/+layout.server.ts +++ b/src/routes/settings/+layout.server.ts @@ -1,6 +1,7 @@ import { collections } from "$lib/server/database"; import type { LayoutServerLoad } from "./$types"; import type { Report } from "$lib/types/Report"; +import { WebSearchProvider } from "$lib/types/WebSearch"; export const load = (async ({ locals, parent }) => { const { assistants } = await parent(); @@ -21,5 +22,6 @@ export const load = (async ({ locals, parent }) => { ...el, reported: reportsByUser.includes(el._id), })), + websearchProviders: Object.values(WebSearchProvider), }; }) satisfies LayoutServerLoad;