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
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 @@ -7,6 +7,7 @@ import WebSearch from './base'
import { BingSearch } from './bing'
import { BingNewsSearch } from './bing-news'
import { ChatboxSearch } from './chatbox-search'
import { QueritSearch } from './querit'
import { TavilySearch } from './tavily'

const MAX_CONTEXT_ITEMS = 10
Expand Down Expand Up @@ -50,6 +51,12 @@ function getSearchProviders() {
)
)
break
case 'querit':
if (!settings.webSearch.queritApiKey) {
throw ChatboxAIAPIError.fromCodeName('querit_api_key_required', 'querit_api_key_required')
}
selectedProviders.push(new QueritSearch(settings.webSearch.queritApiKey))
break
default:
throw new Error(`Unsupported search provider: ${provider}`)
}
Expand Down
52 changes: 52 additions & 0 deletions src/renderer/packages/web-search/querit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ofetch } from 'ofetch'
import WebSearch from './base'
import { SearchResult } from 'src/shared/types'

export class QueritSearch extends WebSearch {
private readonly QUERIT_SEARCH_URL = 'https://api.querit.ai/v1/search'
private apiKey: string

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

async search(query: string, signal?: AbortSignal): Promise<SearchResult> {
try {
const requestBody = { query }
const response = await ofetch(this.QUERIT_SEARCH_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
},
body: requestBody,
signal,
})

// Check error code
if (response.error_code !== 200) {
console.error('Querit search API error:', response.error_code, response.error)
return { items: [] }
}

// Check if results exist
if (!response.results || !response.results.result || !Array.isArray(response.results.result)) {
console.error('Querit search: results not found or not array')
return { items: [] }
}

// Extract result
const items = response.results.result.map((result: any) => ({
title: result.title,
link: result.url,
snippet: result.snippet,
}))

return { items }
} catch (error) {
console.error('Querit search error:', error)
return { items: [] }
}
}
}
80 changes: 79 additions & 1 deletion src/renderer/routes/settings/web-search.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Flex, PasswordInput, Select, Stack, Text, Title, Tooltip } from '@mantine/core'
import { Button, Flex, PasswordInput, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'
import { createFileRoute } from '@tanstack/react-router'
import { ofetch } from 'ofetch'
import { useState } from 'react'
Expand All @@ -17,6 +17,8 @@ export function RouteComponent() {

const [checkingTavily, setCheckingTavily] = useState(false)
const [tavilyAvaliable, setTavilyAvaliable] = useState<boolean>()
const [checkingQuerit, setCheckingQuerit] = useState(false)
const [queritAvaliable, setQueritAvaliable] = useState<boolean>()
const checkTavily = async () => {
if (extension.webSearch.tavilyApiKey) {
setCheckingTavily(true)
Expand All @@ -43,6 +45,29 @@ export function RouteComponent() {
}
}
}
const checkQuerit = async () => {
if (extension.webSearch.queritApiKey) {
setCheckingQuerit(true)
setQueritAvaliable(undefined)
try {
await ofetch('https://api.querit.ai/v1/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${extension.webSearch.queritApiKey}`,
},
body: {
query: 'Chatbox',
},
})
setQueritAvaliable(true)
} catch (e) {
setQueritAvaliable(false)
} finally {
setCheckingQuerit(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: 'querit', label: 'Querit' },
]}
value={extension.webSearch.provider}
onChange={(e) =>
Expand Down Expand Up @@ -275,6 +301,58 @@ export function RouteComponent() {
</Stack>
</Stack>
)}
{/* Querit API Key */}
{extension.webSearch.provider === 'querit' && (
<Stack gap="xs">
<Text fw="600">{t('Querit API Key')}</Text>
<Flex align="center" gap="xs">
<PasswordInput
flex={1}
maw={320}
value={extension.webSearch.queritApiKey}
onChange={(e) => {
setQueritAvaliable(undefined)
setSettings({
extension: {
...extension,
webSearch: {
...extension.webSearch,
queritApiKey: e.currentTarget.value,
},
},
})
}}
placeholder={t('Enter your Querit API Key') || 'Enter your Querit API Key'}
error={queritAvaliable === false}
/>
<Button color="chatbox-gray" variant="light" onClick={checkQuerit} loading={checkingQuerit}>
{t('Check')}
</Button>
</Flex>

{typeof queritAvaliable === 'boolean' ? (
queritAvaliable ? (
<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://www.querit.ai')}
>
{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: '',
queritApiKey: '',
},
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', 'querit']),
tavilyApiKey: z.string().optional(),
tavilySearchDepth: z.string().optional(),
tavilyMaxResults: z.number().optional(),
tavilyTimeRange: z.string().optional(),
tavilyIncludeRawContent: z.string().optional(),
queritApiKey: z.string().optional(),
}),
knowledgeBase: z
.object({
Expand Down