Files
deep-research-web-ui/app/composables/useWebSearch.ts
Jonathan Rampersad 5097c639d2 feat: Added Google PSE as an alternative web search provider (#65)
* Added Google PSE as an alternative web search tool

* Removed unecessary comments

* chore(ConfigManager): moved Google PSE input next to API key

---------

Co-authored-by: AnotiaWang <anotia0202@gmail.com>
2025-04-06 11:18:08 +08:00

117 lines
3.3 KiB
TypeScript

import { tavily } from '@tavily/core'
import Firecrawl from '@mendable/firecrawl-js'
type WebSearchOptions = {
maxResults?: number
/** The search language, e.g. `en`. Only works for Firecrawl. */
lang?: string
}
export type WebSearchResult = {
content: string
url: string
title?: string
}
type WebSearchFunction = (
query: string,
options: WebSearchOptions,
) => Promise<WebSearchResult[]>
export const useWebSearch = (): WebSearchFunction => {
const { config, webSearchApiBase } = useConfigStore()
switch (config.webSearch.provider) {
case 'firecrawl': {
const fc = new Firecrawl({
apiKey: config.webSearch.apiKey,
apiUrl: webSearchApiBase,
})
return async (q: string, o: WebSearchOptions) => {
const results = await fc.search(q, {
...o,
scrapeOptions: {
formats: ['markdown'], // TODO: verify if this actually works
},
})
if (results.error) {
throw new Error(results.error)
}
return results.data
.filter((x) => !!x?.markdown && !!x.url)
.map((r) => ({
content: r.markdown!,
url: r.url!,
title: r.title,
}))
}
}
case 'google-pse': {
const apiKey = config.webSearch.apiKey
const pseId = config.webSearch.googlePseId
return async (q: string, o: WebSearchOptions) => {
if (!apiKey || !pseId) {
throw new Error('Google PSE API key or ID not set')
}
// Construct Google PSE API URL
// Ref: https://developers.google.com/custom-search/v1/using_rest
const searchParams = new URLSearchParams({
key: apiKey,
cx: pseId,
q: q,
num: o.maxResults?.toString() || '5',
});
if (o.lang) {
searchParams.append('lr', `lang_${o.lang}`);
}
const apiUrl = `https://www.googleapis.com/customsearch/v1?${searchParams.toString()}`;
try {
const response = await $fetch<{ items?: Array<{ title: string; link: string; snippet: string }> }>(apiUrl, { method: 'GET' });
if (!response.items) {
return [];
}
// Map response to WebSearchResult format
return response.items.map((item) => ({
content: item.snippet, // Use snippet as content
url: item.link,
title: item.title,
}));
} catch (error: any) {
console.error('Google PSE search failed:', error);
// Attempt to parse Google API error format
const errorMessage = error?.data?.error?.message || error.message || 'Unknown error';
throw new Error(`Google PSE Error: ${errorMessage}`);
}
}
}
case 'tavily':
default: {
const tvly = tavily({
apiKey: config.webSearch.apiKey,
})
return async (q: string, o: WebSearchOptions) => {
const results = await tvly.search(q, {
...o,
searchDepth: config.webSearch.tavilyAdvancedSearch
? 'advanced'
: 'basic',
topic: config.webSearch.tavilySearchTopic,
})
return results.results
.filter((x) => !!x?.content && !!x.url)
.map((r) => ({
content: r.content,
url: r.url,
title: r.title,
}))
}
}
}
}