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": "并发数",