
* 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>
117 lines
3.3 KiB
TypeScript
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,
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
}
|