mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-02-06 19:11:09 +08:00
* feat(WebSearch, RAG): support RAG for external websearch * refactor(WebSearch): handle content limit in service * refactor: update migrate * refactor: UI, constants, types * refactor: migrate contentLimit to cutoffLimit * refactor: update default rag document count * refactor: add a helper function for merging references * refactor: reference filtering * feat: feedback for websearch phases * feat: support cutoff by token * refactor: add a warning and fix the bound of cutoff limit * fix: not pass `dimensions` if it is not set by the user * refactor: update i18n and error message * refactor: improve UI * fix: cutoff unit style
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
import { WebSearchState } from '@renderer/store/websearch'
|
||
import { WebSearchProvider, WebSearchProviderResponse } from '@renderer/types'
|
||
import { BochaSearchParams, BochaSearchResponse } from '@renderer/utils/bocha'
|
||
|
||
import BaseWebSearchProvider from './BaseWebSearchProvider'
|
||
|
||
export default class BochaProvider extends BaseWebSearchProvider {
|
||
constructor(provider: WebSearchProvider) {
|
||
super(provider)
|
||
if (!this.apiKey) {
|
||
throw new Error('API key is required for Bocha provider')
|
||
}
|
||
if (!this.apiHost) {
|
||
throw new Error('API host is required for Bocha provider')
|
||
}
|
||
}
|
||
|
||
public async search(query: string, websearch: WebSearchState): Promise<WebSearchProviderResponse> {
|
||
try {
|
||
if (!query.trim()) {
|
||
throw new Error('Search query cannot be empty')
|
||
}
|
||
|
||
const headers = {
|
||
'Content-Type': 'application/json',
|
||
Authorization: `Bearer ${this.apiKey}`
|
||
}
|
||
|
||
const params: BochaSearchParams = {
|
||
query,
|
||
count: websearch.maxResults,
|
||
exclude: websearch.excludeDomains.join(','),
|
||
freshness: websearch.searchWithTime ? 'oneDay' : 'noLimit',
|
||
summary: true,
|
||
page: 1
|
||
}
|
||
|
||
const response = await fetch(`${this.apiHost}/v1/web-search`, {
|
||
method: 'POST',
|
||
body: JSON.stringify(params),
|
||
headers: {
|
||
...this.defaultHeaders(),
|
||
...headers
|
||
}
|
||
})
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`Bocha search failed: ${response.status} ${response.statusText}`)
|
||
}
|
||
|
||
const resp: BochaSearchResponse = await response.json()
|
||
if (resp.code !== 200) {
|
||
throw new Error(`Bocha search failed: ${resp.msg}`)
|
||
}
|
||
return {
|
||
query: resp.data.queryContext.originalQuery,
|
||
results: resp.data.webPages.value.map((result) => ({
|
||
title: result.name,
|
||
// 优先使用 summary(更详细),如果没有则使用 snippet
|
||
content: result.summary || result.snippet || '',
|
||
url: result.url
|
||
}))
|
||
}
|
||
} catch (error) {
|
||
console.error('Bocha search failed:', error)
|
||
throw new Error(`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||
}
|
||
}
|
||
}
|