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>
This commit is contained in:
Jonathan Rampersad
2025-04-05 23:18:08 -04:00
committed by GitHub
parent 651f445803
commit 5097c639d2
6 changed files with 97 additions and 1 deletions

View File

@ -91,6 +91,12 @@
link: 'https://www.firecrawl.dev/app/api-keys', link: 'https://www.firecrawl.dev/app/api-keys',
supportsCustomApiBase: true, supportsCustomApiBase: true,
}, },
{
label: 'Google PSE',
value: 'google-pse',
help: 'settings.webSearch.providers.google-pse.help',
link: 'https://programmablesearchengine.google.com/', // Link to Google PSE console
},
]) ])
const tavilySearchTopicOptions = ['general', 'news', 'finance'] const tavilySearchTopicOptions = ['general', 'news', 'finance']
const selectedAiProvider = computed(() => const selectedAiProvider = computed(() =>
@ -315,6 +321,26 @@
:placeholder="$t('settings.webSearch.apiKey')" :placeholder="$t('settings.webSearch.apiKey')"
/> />
</UFormField> </UFormField>
<template v-if="config.webSearch.provider === 'google-pse'">
<UFormField
:label="
$t('settings.webSearch.providers.google-pse.pseIdLabel')
"
required
>
<UInput
v-model="config.webSearch.googlePseId"
class="w-full"
:placeholder="
$t(
'settings.webSearch.providers.google-pse.pseIdPlaceholder',
)
"
/>
</UFormField>
</template>
<UFormField <UFormField
v-if="selectedWebSearchProvider?.supportsCustomApiBase" v-if="selectedWebSearchProvider?.supportsCustomApiBase"
:label="$t('settings.webSearch.apiBase')" :label="$t('settings.webSearch.apiBase')"

View File

@ -46,6 +46,50 @@ export const useWebSearch = (): WebSearchFunction => {
})) }))
} }
} }
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': case 'tavily':
default: { default: {
const tvly = tavily({ const tvly = tavily({

View File

@ -9,7 +9,7 @@ export type ConfigAiProvider =
| 'deepseek' | 'deepseek'
| 'ollama' | 'ollama'
export type ConfigWebSearchProvider = 'tavily' | 'firecrawl' export type ConfigWebSearchProvider = 'tavily' | 'firecrawl' | 'google-pse'
export interface ConfigAi { export interface ConfigAi {
provider: ConfigAiProvider provider: ConfigAiProvider
@ -31,6 +31,7 @@ export interface ConfigWebSearch {
tavilyAdvancedSearch?: boolean tavilyAdvancedSearch?: boolean
/** Tavily: search topic. Defaults to `general` */ /** Tavily: search topic. Defaults to `general` */
tavilySearchTopic?: 'general' | 'news' | 'finance' tavilySearchTopic?: 'general' | 'news' | 'finance'
googlePseId?: string; // Google PSE ID
} }
export interface Config { export interface Config {
@ -47,6 +48,7 @@ function validateConfig(config: Config) {
if (ws.provider === 'tavily' && !ws.apiKey) return false if (ws.provider === 'tavily' && !ws.apiKey) return false
// Either apiBase or apiKey is required for firecrawl // Either apiBase or apiKey is required for firecrawl
if (ws.provider === 'firecrawl' && !ws.apiBase && !ws.apiKey) return false if (ws.provider === 'firecrawl' && !ws.apiBase && !ws.apiKey) return false
if (ws.provider === 'google-pse' && (!ws.apiKey || !ws.googlePseId)) return false; // Require API Key and PSE ID
if (typeof ws.concurrencyLimit !== 'undefined' && ws.concurrencyLimit! < 1) if (typeof ws.concurrencyLimit !== 'undefined' && ws.concurrencyLimit! < 1)
return false return false
return true return true

View File

@ -49,6 +49,14 @@
}, },
"firecrawl": { "firecrawl": {
"help": "Get one API key at {0} if you are using the official service." "help": "Get one API key at {0} if you are using the official service."
},
"google-pse": {
"title": "Google PSE",
"help": "Uses Google Programmable Search Engine. Requires an API Key and a PSE ID from Google Cloud Console / PSE Console. Find details at {0}.",
"apiKeyLabel": "Google API Key",
"apiKeyPlaceholder": "Enter your Google API Key",
"pseIdLabel": "Programmable Search Engine ID (cx)",
"pseIdPlaceholder": "Enter your PSE ID (cx value)"
} }
}, },
"concurrencyLimitHelp": "Limit the concurrent search tasks. This is useful to avoid overloading the search provider and causing requests to fail.", "concurrencyLimitHelp": "Limit the concurrent search tasks. This is useful to avoid overloading the search provider and causing requests to fail.",

View File

@ -49,6 +49,14 @@
}, },
"firecrawl": { "firecrawl": {
"help": "Ontvang een API-sleutel bij {0} als u de officiële service gebruikt." "help": "Ontvang een API-sleutel bij {0} als u de officiële service gebruikt."
},
"google-pse": {
"title": "Google PSE",
"help": "Gebruikt Google Programmable Search Engine. Vereist een API-sleutel en een PSE-ID van Google Cloud Console / PSE Console. Vind details op {0}.",
"apiKeyLabel": "Google API-sleutel",
"apiKeyPlaceholder": "Voer uw Google API-sleutel in",
"pseIdLabel": "Programmable Search Engine ID (cx)",
"pseIdPlaceholder": "Voer uw PSE ID (cx-waarde) in"
} }
}, },
"concurrencyLimitHelp": "Beperk de gelijktijdige zoektaken. Dit is handig om te voorkomen dat de zoekmachine overbelast raakt en verzoeken mislukken.", "concurrencyLimitHelp": "Beperk de gelijktijdige zoektaken. Dit is handig om te voorkomen dat de zoekmachine overbelast raakt en verzoeken mislukken.",

View File

@ -49,6 +49,14 @@
"searchTopic": "搜索领域", "searchTopic": "搜索领域",
"advancedSearch": "高质量搜索", "advancedSearch": "高质量搜索",
"searchTopicHelp": "搜索特定领域的信息,获得更精确的结果。默认为“通用” (general)。" "searchTopicHelp": "搜索特定领域的信息,获得更精确的结果。默认为“通用” (general)。"
},
"google-pse": {
"title": "Google PSE",
"help": "使用 Google 可编程搜索引擎。需要来自 Google Cloud Console / PSE Console 的 API 密钥和 PSE ID。在 {0} 查看详情。",
"apiKeyLabel": "Google API 密钥",
"apiKeyPlaceholder": "输入你的 Google API 密钥",
"pseIdLabel": "可编程搜索引擎 ID (cx)",
"pseIdPlaceholder": "输入你的 PSE ID (cx 值)"
} }
}, },
"concurrencyLimit": "并发数", "concurrencyLimit": "并发数",