diff --git a/server/.env.template b/server/.env.template index 11becb5..b471f93 100644 --- a/server/.env.template +++ b/server/.env.template @@ -6,4 +6,5 @@ GOOGLE_KEY=XXX OPEN_WEATHER_KEY=XXX BING_KEY=XXX FINNHUB_KEY=XXX -WOLFRAM_KEY=XXX \ No newline at end of file +WOLFRAM_KEY=XXX +BRAVE_KEY=XXX diff --git a/server/src/services/search.ts b/server/src/services/search.ts index 60e04b0..bdebb8d 100644 --- a/server/src/services/search.ts +++ b/server/src/services/search.ts @@ -1,27 +1,60 @@ import axios from "axios"; -const getKey = () => process.env.BING_KEY as string; +// Function to get the Bing API key +const getBingKey = () => process.env.BING_KEY as string | undefined; -interface WebPagesItem { +// Function to get the Brave API key +const getBraveKey = () => process.env.BRAVE_KEY as string; + +// Bing Search types +interface BingWebPagesItem { name: string; snippet: string; } -interface WebPages { +interface BingWebPages { webSearchUrl: string; totalEstimatedMatches: number; - value: WebPagesItem[]; + value: BingWebPagesItem[]; +} + +interface BingSearchResponse { + webPages: BingWebPages; +} + +// Brave Search types +interface BraveWebPagesItem { + title: string; + url: string; + description: string; +} + +interface BraveQuery { + original: string; } -interface SearchResponse { - webPages: WebPages; +interface BraveWebPages { + type: string; + results: BraveWebPagesItem[]; } -export const getSearchResults = async (query: string) => { +interface BraveResponse { + query: BraveQuery; + web: BraveWebPages; +} + +// Common result type for both search engines +interface SearchResult { + title: string; + snippet: string; +} + +// Bing search implementation +async function bingSearch(query: string): Promise { const searchClient = axios.create({ baseURL: "https://api.bing.microsoft.com/v7.0", headers: { - "Ocp-Apim-Subscription-Key": getKey(), + "Ocp-Apim-Subscription-Key": getBingKey(), }, }); @@ -32,10 +65,52 @@ export const getSearchResults = async (query: string) => { }, }); - const { webPages } = res.data as SearchResponse; + const { webPages } = res.data as BingSearchResponse; return webPages.value.map((page) => ({ title: page.name, snippet: page.snippet, })); -}; +} + +// Brave search implementation +async function braveSearch(query: string): Promise { + const searchClient = axios.create({ + baseURL: "https://api.search.brave.com/res/v1/web", + headers: { + "X-Subscription-Token": getBraveKey(), + "Accept": "application/json" + }, + }); + + const res = await searchClient.get("/search", { + params: { + q: query, + }, + }); + + const { web } = res.data as BraveResponse; + + return web.results.map((page) => ({ + title: page.title, + snippet: page.description, + })); +} + +// Combined search implementation +export const getSearchResults = async (query: string): Promise => { + const bingKey = getBingKey(); + + try { + if (bingKey) { + // If Bing key exists, use Bing search + return await bingSearch(query); + } else { + // Otherwise fall back to Brave search + return await braveSearch(query); + } + } catch (error) { + console.error("Search failed:", error); + throw error; + } +}; \ No newline at end of file