Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/renderer/packages/web-search/bocha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ofetch } from 'ofetch'
import WebSearch from './base'
import { SearchResult } from 'src/shared/types'

export class BoChaSearch extends WebSearch {
private readonly BOCHA_SEARCH_URL = 'https://api.bochaai.com/v1/web-search'

private apiKey: string

constructor(
apiKey: string,
) {
super()
this.apiKey = apiKey
}

async search(query: string, signal?: AbortSignal): Promise<SearchResult> {
try {
const requestBody = {
query: query,
}
const response = await ofetch(this.BOCHA_SEARCH_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
},
body: requestBody,
signal,
})
if (response.code !== 200) {
console.error('BoCha search API error:', response.code)
return { items: [] }
}
const items = response.data.webPages.value.map((page: any) => ({
title: page.name,
link: page.url,
snippet: page.snippet,
}))

return { items }
} catch (error) {
console.error('BoCha search error:', error)
return { items: [] }
}
}
}
7 changes: 7 additions & 0 deletions src/renderer/packages/web-search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { BingSearch } from './bing'
import { BingNewsSearch } from './bing-news'
import { ChatboxSearch } from './chatbox-search'
import { TavilySearch } from './tavily'
import { BoChaSearch} from './bocha'

const MAX_CONTEXT_ITEMS = 10

Expand Down Expand Up @@ -50,6 +51,12 @@ function getSearchProviders() {
)
)
break
case 'bocha':
if (!settings.webSearch.bochaApiKey) {
throw ChatboxAIAPIError.fromCodeName('bocha_api_key_required', 'bocha_api_key_required')
}
selectedProviders.push(new BoChaSearch(settings.webSearch.bochaApiKey))
break
default:
throw new Error(`Unsupported search provider: ${provider}`)
}
Expand Down
78 changes: 78 additions & 0 deletions src/renderer/routes/settings/web-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,31 @@ export function RouteComponent() {
}
}
}
const [checkingBoCha, setCheckingBoCha] = useState(false)
const [bochaAvaliable, setBoChaAvaliable] = useState<boolean>()
const checkBoCha = async () => {
if (extension.webSearch.bochaApiKey) {
setCheckingBoCha(true)
setBoChaAvaliable(undefined)
try {
await ofetch('https://api.bochaai.com/v1/web-search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${extension.webSearch.bochaApiKey}`,
},
body: {
query: 'Chatbox',
},
})
setBoChaAvaliable(true)
} catch (e) {
setBoChaAvaliable(false)
} finally {
setCheckingBoCha(false)
}
}
}

return (
<Stack p="md" gap="xxl">
Expand All @@ -54,6 +79,7 @@ export function RouteComponent() {
{ value: 'build-in', label: 'Chatbox Search (Pro)' },
{ value: 'bing', label: 'Bing Search (Free)' },
{ value: 'tavily', label: 'Tavily' },
{value: 'bocha', label: 'BoCha' },
]}
value={extension.webSearch.provider}
onChange={(e) =>
Expand Down Expand Up @@ -275,6 +301,58 @@ export function RouteComponent() {
</Stack>
</Stack>
)}
{/* Bocha API Key */}
{extension.webSearch.provider === 'bocha' && (
<Stack gap="xs">
<Text fw="600">{t('BoCha API Key')}</Text>
<Flex align="center" gap="xs">
<PasswordInput
flex={1}
maw={320}
value={extension.webSearch.bochaApiKey}
onChange={(e) => {
setBoChaAvaliable(undefined)
setSettings({
extension: {
...extension,
webSearch: {
...extension.webSearch,
bochaApiKey: e.currentTarget.value,
},
},
})
}}
placeholder={t('Enter your BoCha API Key') || 'Enter your BoCha API Key'}
error={bochaAvaliable === false}
/>
<Button color="chatbox-gray" variant="light" onClick={checkBoCha} loading={checkingBoCha}>
{t('Check')}
</Button>
</Flex>

{typeof bochaAvaliable === 'boolean' ? (
bochaAvaliable ? (
<Text size="xs" c="chatbox-success">
{t('Connection successful!')}
</Text>
) : (
<Text size="xs" c="chatbox-error">
{t('API key invalid!')}
</Text>
)
) : null}

<Button
variant="transparent"
size="compact-xs"
px={0}
className="self-start"
onClick={() => platform.openLink('https://bocha.cn/')}
>
{t('Get API Key')}
</Button>
</Stack>
)}
</Stack>
)
}
1 change: 1 addition & 0 deletions src/shared/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export function settings(): Settings {
webSearch: {
provider: 'build-in',
tavilyApiKey: '',
bochaApiKey: '',
},
knowledgeBase: {
models: {
Expand Down
3 changes: 2 additions & 1 deletion src/shared/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,13 @@ const ShortcutSettingSchema = z.object({

const ExtensionSettingsSchema = z.object({
webSearch: z.object({
provider: z.enum(['build-in', 'bing', 'tavily']),
provider: z.enum(['build-in', 'bing', 'tavily', 'bocha']),
tavilyApiKey: z.string().optional(),
tavilySearchDepth: z.string().optional(),
tavilyMaxResults: z.number().optional(),
tavilyTimeRange: z.string().optional(),
tavilyIncludeRawContent: z.string().optional(),
bochaApiKey: z.string().optional(),
}),
knowledgeBase: z
.object({
Expand Down