diff --git a/app/components/ConfigManager.vue b/app/components/ConfigManager.vue index f0da212..a31c3e7 100644 --- a/app/components/ConfigManager.vue +++ b/app/components/ConfigManager.vue @@ -91,6 +91,12 @@ link: 'https://www.firecrawl.dev/app/api-keys', 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 selectedAiProvider = computed(() => @@ -315,6 +321,26 @@ :placeholder="$t('settings.webSearch.apiKey')" /> + + + { })) } } + 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({ diff --git a/app/stores/config.ts b/app/stores/config.ts index 7794bd2..c39774d 100644 --- a/app/stores/config.ts +++ b/app/stores/config.ts @@ -9,7 +9,7 @@ export type ConfigAiProvider = | 'deepseek' | 'ollama' -export type ConfigWebSearchProvider = 'tavily' | 'firecrawl' +export type ConfigWebSearchProvider = 'tavily' | 'firecrawl' | 'google-pse' export interface ConfigAi { provider: ConfigAiProvider @@ -31,6 +31,7 @@ export interface ConfigWebSearch { tavilyAdvancedSearch?: boolean /** Tavily: search topic. Defaults to `general` */ tavilySearchTopic?: 'general' | 'news' | 'finance' + googlePseId?: string; // Google PSE ID } export interface Config { @@ -47,6 +48,7 @@ function validateConfig(config: Config) { if (ws.provider === 'tavily' && !ws.apiKey) return false // Either apiBase or apiKey is required for firecrawl 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) return false return true diff --git a/i18n/en.json b/i18n/en.json index fda8a0b..60557c1 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -49,6 +49,14 @@ }, "firecrawl": { "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.", diff --git a/i18n/nl.json b/i18n/nl.json index db8875a..1f9274e 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -49,6 +49,14 @@ }, "firecrawl": { "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.", diff --git a/i18n/zh.json b/i18n/zh.json index fbeee64..ef28cae 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -49,6 +49,14 @@ "searchTopic": "搜索领域", "advancedSearch": "高质量搜索", "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": "并发数",