cherry-studio/src/main/services/SearchService.ts
LiuVaayne e2e7d0f46f feat(search): support using google as default search provider (#4569)
* feat(websearch): implement search window functionality and enhance search service

* feat(DefaultProvider): integrate @mozilla/readability for improved content parsing

* Add LocalSearchProvider for web page scraping

AI: Change `provider` from private to protected in BaseWebSearchProvider and implement LocalSearchProvider for web searching with browser-based content extraction.

* Add web search provider management features

Implement addWebSearchProvider function to prevent duplicates,
automatically load default providers on initialization, fix
LocalSearchProvider implementation, and update local provider
identification logic.

* Improve web search with specialized search engine parsers

Add dedicated parsers for Google, Bing, and Baidu search results,
replacing the generic URL extraction approach. Enhance page loading
with proper wait mechanisms and window cleanup. Remove DuckDuckGo
provider as it's no longer supported.

* Simplify DefaultProvider to unimplemented placeholder

* Remove default search engine from initial state

* Improve web search providers config and display

Add configuration for local search providers, remove empty apiKey fields,
and enhance the UI by sorting providers alphabetically and showing
whether they require an API key.

* Add stderr logging for MCP servers

* Make search window initially hidden
2025-04-10 12:29:09 +08:00

83 lines
2.5 KiB
TypeScript

import { is } from '@electron-toolkit/utils'
import { BrowserWindow } from 'electron'
export class SearchService {
private static instance: SearchService | null = null
private searchWindows: Record<string, BrowserWindow> = {}
public static getInstance(): SearchService {
if (!SearchService.instance) {
SearchService.instance = new SearchService()
}
return SearchService.instance
}
constructor() {
// Initialize the service
}
private async createNewSearchWindow(uid: string): Promise<BrowserWindow> {
const newWindow = new BrowserWindow({
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
devTools: is.dev
}
})
newWindow.webContents.session.webRequest.onBeforeSendHeaders({ urls: ['*://*/*'] }, (details, callback) => {
const headers = {
...details.requestHeaders,
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
callback({ requestHeaders: headers })
})
this.searchWindows[uid] = newWindow
newWindow.on('closed', () => {
delete this.searchWindows[uid]
})
return newWindow
}
public async openSearchWindow(uid: string): Promise<void> {
await this.createNewSearchWindow(uid)
}
public async closeSearchWindow(uid: string): Promise<void> {
const window = this.searchWindows[uid]
if (window) {
window.close()
delete this.searchWindows[uid]
}
}
public async openUrlInSearchWindow(uid: string, url: string): Promise<any> {
let window = this.searchWindows[uid]
if (window) {
await window.loadURL(url)
} else {
window = await this.createNewSearchWindow(uid)
await window.loadURL(url)
}
// Get the page content after loading the URL
// Wait for the page to fully load before getting the content
await new Promise<void>((resolve) => {
const loadTimeout = setTimeout(() => resolve(), 10000) // 10 second timeout
window.webContents.once('did-finish-load', () => {
clearTimeout(loadTimeout)
// Small delay to ensure JavaScript has executed
setTimeout(resolve, 500)
})
})
// Get the page content after ensuring it's fully loaded
const content = await window.webContents.executeJavaScript('document.documentElement.outerHTML')
return content
}
}
export const searchService = SearchService.getInstance()