Skip to content

Add custom search engines in settings #1810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/lib/server/websearch/runWebSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -27,7 +28,8 @@ export async function* runWebSearch(
conv: Conversation,
messages: Message[],
ragSettings?: Assistant["rag"],
query?: string
query?: string,
provider?: WebSearchProvider
): AsyncGenerator<MessageWebSearchUpdate, WebSearch, undefined> {
const prompt = messages[messages.length - 1].content;
const createdAt = new Date();
Expand All @@ -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
Expand Down
23 changes: 22 additions & 1 deletion src/lib/server/websearch/search/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ import searchSearxng from "./endpoints/searxng";
import searchSearchApi from "./endpoints/searchApi";
import searchBing from "./endpoints/bing";

const providerMap: Record<WebSearchProvider, (q: string) => Promise<WebSearchSource[]>> = {
[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;
Expand All @@ -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<WebSearchSource[]> {
export async function searchWeb(
query: string,
provider?: WebSearchProvider
): Promise<WebSearchSource[]> {
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);
Expand Down
11 changes: 11 additions & 0 deletions src/lib/stores/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -18,6 +19,7 @@ type SettingsStore = {
tools?: Array<string>;
disableStream: boolean;
directPaste: boolean;
preferredWebSearchEngine: WebSearchProvider;
};

type SettingsStoreWritable = Writable<SettingsStore> & {
Expand All @@ -31,6 +33,15 @@ export function useSettingsStore() {
export function createSettingsStore(initialValue: Omit<SettingsStore, "recentlySaved">) {
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<SettingsStore>) {
Expand Down
1 change: 1 addition & 0 deletions src/lib/types/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface Settings extends Timestamps {
tools?: string[];
disableStream: boolean;
directPaste: boolean;
preferredSearchEngine?: import("./WebSearch").WebSearchProvider;
}

export type SettingsEditable = Omit<Settings, "ethicsModalAcceptedAt" | "createdAt" | "updatedAt">;
Expand Down
2 changes: 2 additions & 0 deletions src/lib/types/WebSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ export enum WebSearchProvider {
YOU = "You.com",
SEARXNG = "SearXNG",
BING = "Bing",
SEPRER = "serper",
DUCKDUCKGO = "DuckDuckGo",
}
81 changes: 81 additions & 0 deletions src/routes/settings/(nav)/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<script lang="ts">
/* ------------------------------------------------------------
imports
------------------------------------------------------------ */
import { pageSettings } from "$lib/stores/settings";
import { afterUpdate } from "svelte";
import CarbonCheckmark from "~icons/carbon/checkmark";
import CarbonInformation from "~icons/carbon/information";
import type { WebSearchProvider } from "$lib/types/WebSearch";

/* ------------------------------------------------------------
props (from +layout.server.ts)
------------------------------------------------------------ */
export let data: {
websearchProviders: WebSearchProvider[];
};

/* ------------------------------------------------------------
local helpers
------------------------------------------------------------ */
const providerLabels: Record<WebSearchProvider, string> = {
serper: "Serper (Google proxy)",
google: "Google (via Serper)",
bing: "Bing",
duckduckgo: "DuckDuckGo (SearxNG)",
};

let savedFlash = false;

// flash “Saved” check icons
afterUpdate(() => {
if ($pageSettings.recentlySaved && !savedFlash) {
savedFlash = true;
setTimeout(() => (savedFlash = false), 2000);
}
});
</script>

<!-- ──────────────────────────────────────────────── -->
<!-- Header -->
<!-- ──────────────────────────────────────────────── -->
<div class="flex items-center gap-3 pb-6">
<h1 class="text-2xl font-bold">Search Engine</h1>

{#if savedFlash}
<CarbonCheckmark class="h-5 w-5 text-green-600" />
{/if}
</div>

<p class="mb-6 text-sm text-gray-600 flex items-start gap-2">
<CarbonInformation class="mt-0.5 shrink-0 text-gray-500" />
Choosing a provider changes which external search API HuggingChat calls
when the <em>Web Search</em> tool is triggered.
</p>

<!-- ──────────────────────────────────────────────── -->
<!-- Provider list (radio style) -->
<!-- ──────────────────────────────────────────────── -->
<div class="space-y-4">
{#each data.websearchProviders as provider}
<label
class="flex cursor-pointer items-center rounded-lg border border-gray-300 p-4 hover:bg-gray-50"
>
<input
type="radio"
class="mr-4 h-4 w-4 accent-black"
name="search-provider"
bind:group={$pageSettings.preferredSearchEngine}
value={provider}
/>
<span class="mr-auto">{providerLabels[provider] ?? provider}</span>

{#if provider === $pageSettings.preferredSearchEngine}
<span
class="rounded-md bg-black px-2 py-0.5 text-xs font-semibold leading-none text-white"
>Selected</span
>
{/if}
</label>
{/each}
</div>
2 changes: 2 additions & 0 deletions src/routes/settings/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -21,5 +22,6 @@ export const load = (async ({ locals, parent }) => {
...el,
reported: reportsByUser.includes(el._id),
})),
websearchProviders: Object.values(WebSearchProvider),
};
}) satisfies LayoutServerLoad;
Loading