feat: Add Brave Search API support

This commit is contained in:
n4ze3m 2024-12-13 20:03:52 +05:30
parent 44e2057ff4
commit fd6eea3e84
22 changed files with 248 additions and 10 deletions

View File

@ -70,6 +70,10 @@
"url": {
"label": "SearXNG URL"
}
},
"braveApi": {
"label": "Brave API Nøgle",
"placeholder": "Indtast din Brave API nøgle"
}
},
"system": {

View File

@ -70,6 +70,10 @@
"url": {
"label": "SearXNG-URL"
}
},
"braveApi": {
"label": "Brave API-Schlüssel",
"placeholder": "Geben Sie Ihren Brave API-Schlüssel ein"
}
},
"system": {
@ -78,7 +82,8 @@
"label": "System zurücksetzen",
"button": "Alles zurücksetzen",
"confirm": "Sind Sie sicher, dass Sie einen Systemreset durchführen möchten? Dies löscht alle Daten und kann nicht rückgängig gemacht werden."
}, "export": {
},
"export": {
"label": "Chatverlauf, Wissensbasis und Prompts exportieren",
"button": "Daten exportieren",
"success": "Export erfolgreich"

View File

@ -70,6 +70,10 @@
"url": {
"label": "SearXNG URL"
}
},
"braveApi": {
"label": "Brave API Key",
"placeholder": "Enter your Brave API key"
}
},
"system": {

View File

@ -70,6 +70,10 @@
"url": {
"label": "URL de SearXNG"
}
},
"braveApi": {
"label": "Clave API de Brave",
"placeholder": "Ingrese su clave API de Brave"
}
},
"system": {

View File

@ -67,6 +67,10 @@
"url": {
"label": "آدرس SearXNG"
}
},
"braveApi": {
"label": "کلید API بریو",
"placeholder": "کلید API بریو خود را وارد کنید"
}
},
"system": {

View File

@ -70,6 +70,10 @@
"url": {
"label": "URL SearXNG"
}
},
"braveApi": {
"label": "Clé API Brave",
"placeholder": "Entrez votre clé API Brave"
}
},
"system": {

View File

@ -70,6 +70,10 @@
"url": {
"label": "URL SearXNG"
}
},
"braveApi": {
"label": "Chiave API Brave",
"placeholder": "Inserisci la tua chiave API Brave"
}
},
"system": {

View File

@ -73,6 +73,10 @@
"url": {
"label": "SearXNG URL"
}
},
"braveApi": {
"label": "Brave APIキー",
"placeholder": "Brave APIキーを入力してください"
}
},
"system": {

View File

@ -73,6 +73,10 @@
"url": {
"label": "SearXNG URL"
}
},
"braveApi": {
"label": "Brave API 키",
"placeholder": "Brave API 키를 입력하세요"
}
},
"system": {

View File

@ -73,6 +73,10 @@
"url": {
"label": "SearXNG URL"
}
},
"braveApi": {
"label": "ബ്രേവ് API കീ",
"placeholder": "നിങ്ങളുടെ ബ്രേവ് API കീ നൽകുക"
}
},
"system": {

View File

@ -70,6 +70,10 @@
"url": {
"label": "SearXNG URL"
}
},
"braveApi": {
"label": "Brave API Nøkkel",
"placeholder": "Skriv inn din Brave API nøkkel"
}
},
"system": {

View File

@ -70,6 +70,10 @@
"url": {
"label": "URL do SearXNG"
}
},
"braveApi": {
"label": "Chave da API do Brave",
"placeholder": "Digite sua chave da API do Brave"
}
},
"system": {

View File

@ -71,6 +71,10 @@
"url": {
"label": "URL-адрес SearXNG"
}
},
"braveApi": {
"label": "API-ключ Brave",
"placeholder": "Введите ваш API-ключ Brave"
}
},
"system": {

View File

@ -70,6 +70,10 @@
"url": {
"label": "SearXNG URL"
}
},
"braveApi": {
"label": "Brave API-nyckel",
"placeholder": "Ange din Brave API-nyckel"
}
},
"system": {

View File

@ -70,6 +70,10 @@
"url": {
"label": "SearXNG URL-адреса"
}
},
"braveApi": {
"label": "Ключ API Brave",
"placeholder": "Введіть ваш ключ API Brave"
}
},
"system": {

View File

@ -73,6 +73,10 @@
"url": {
"label": "SearXNG 网址"
}
},
"braveApi": {
"label": "Brave API 密钥",
"placeholder": "输入您的 Brave API 密钥"
}
},
"system": {

View File

@ -27,7 +27,7 @@ export const PlaygroundChat = () => {
<>
<div
ref={containerRef}
className="custom-scrollbar grow flex flex-col md:translate-x-0 transition-transform duration-300 ease-in-out overflow-y-auto h-[calc(100vh-160px)]">
className="custom-scrollbar grow flex flex-col md:translate-x-0 transition-transform duration-300 ease-in-out overflow-y-auto h-[calc(100vh-160px)]">
{messages.length === 0 && (
<div className="mt-32">
<PlaygroundEmpty />

View File

@ -2,7 +2,7 @@ import { SaveButton } from "@/components/Common/SaveButton"
import { getSearchSettings, setSearchSettings } from "@/services/search"
import { SUPPORTED_SERACH_PROVIDERS } from "@/utils/search-provider"
import { useForm } from "@mantine/form"
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { useQuery } from "@tanstack/react-query"
import { Select, Skeleton, Switch, InputNumber, Input } from "antd"
import { useTranslation } from "react-i18next"
@ -16,7 +16,8 @@ export const SearchModeSettings = () => {
totalSearchResults: 0,
visitSpecificWebsite: false,
searxngURL: "",
searxngJSONMode: false
searxngJSONMode: false,
braveApiKey: "",
}
})
@ -81,6 +82,25 @@ export const SearchModeSettings = () => {
</div>
</>
)}
{form.values.searchProvider === "brave-api" && (
<>
<div className="flex sm:flex-row flex-col space-y-4 sm:space-y-0 sm:justify-between">
<span className="text-gray-700 dark:text-neutral-50">
{t("generalSettings.webSearch.braveApi.label")}
</span>
<div>
<Input.Password
placeholder={t(
"generalSettings.webSearch.braveApi.placeholder"
)}
required
className="w-full mt-4 sm:mt-0 sm:w-[200px]"
{...form.getInputProps("braveApiKey")}
/>
</div>
</div>
</>
)}
<div className="flex sm:flex-row flex-col space-y-4 sm:space-y-0 sm:justify-between">
<span className="text-gray-700 dark:text-neutral-50 ">
{t("generalSettings.webSearch.searchMode.label")}

View File

@ -1,6 +1,9 @@
import { Storage } from "@plasmohq/storage"
const storage = new Storage()
const storage2 = new Storage({
area: "local"
})
const TOTAL_SEARCH_RESULTS = 2
const DEFAULT_PROVIDER = "google"
@ -80,10 +83,20 @@ export const setSearxngURL = async (searxngURL: string) => {
await storage.set("searxngURL", searxngURL)
}
export const getBraveApiKey = async () => {
const braveApiKey = await storage2.get("braveApiKey")
return braveApiKey || ""
}
export const setBraveApiKey = async (braveApiKey: string) => {
await storage2.set("braveApiKey", braveApiKey)
}
export const getSearchSettings = async () => {
const [isSimpleInternetSearch, searchProvider, totalSearchResult, visitSpecificWebsite,
searxngURL,
searxngJSONMode
searxngJSONMode,
braveApiKey
] =
await Promise.all([
getIsSimpleInternetSearch(),
@ -91,7 +104,8 @@ export const getSearchSettings = async () => {
totalSearchResults(),
getIsVisitSpecificWebsite(),
getSearxngURL(),
isSearxngJSONMode()
isSearxngJSONMode(),
getBraveApiKey()
])
return {
@ -100,7 +114,8 @@ export const getSearchSettings = async () => {
totalSearchResults: totalSearchResult,
visitSpecificWebsite,
searxngURL,
searxngJSONMode
searxngJSONMode,
braveApiKey
}
}
@ -110,14 +125,16 @@ export const setSearchSettings = async ({
totalSearchResults,
visitSpecificWebsite,
searxngJSONMode,
searxngURL
searxngURL,
braveApiKey
}: {
isSimpleInternetSearch: boolean
searchProvider: string
totalSearchResults: number
visitSpecificWebsite: boolean
searxngURL: string
searxngJSONMode: boolean
searxngJSONMode: boolean,
braveApiKey: string
}) => {
await Promise.all([
setIsSimpleInternetSearch(isSimpleInternetSearch),
@ -125,6 +142,7 @@ export const setSearchSettings = async ({
setTotalSearchResults(totalSearchResults),
setIsVisitSpecificWebsite(visitSpecificWebsite),
setSearxngJSONMode(searxngJSONMode),
setSearxngURL(searxngURL)
setSearxngURL(searxngURL),
setBraveApiKey(braveApiKey)
])
}

View File

@ -18,5 +18,9 @@ export const SUPPORTED_SERACH_PROVIDERS = [
{
label: "Searxng",
value: "searxng"
},
{
label: "Brave Search API",
value: "brave-api"
}
]

View File

@ -0,0 +1,128 @@
import { cleanUrl } from "~/libs/clean-url"
import { getIsSimpleInternetSearch, totalSearchResults, getBraveApiKey } from "@/services/search"
import { pageAssistEmbeddingModel } from "@/models/embedding"
import type { Document } from "@langchain/core/documents"
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
import { MemoryVectorStore } from "langchain/vectorstores/memory"
import { PageAssistHtmlLoader } from "~/loader/html"
import {
defaultEmbeddingChunkOverlap,
defaultEmbeddingChunkSize,
defaultEmbeddingModelForRag,
getOllamaURL
} from "~/services/ollama"
interface BraveAPIResult {
title: string
url: string
description: string
}
interface BraveAPIResponse {
web: {
results: BraveAPIResult[]
}
}
export const braveAPISearch = async (query: string) => {
const braveApiKey = await getBraveApiKey()
if (!braveApiKey || braveApiKey.trim() === "") {
throw new Error("Brave API key not configured")
}
const results = await apiBraveSearch(braveApiKey, query)
const TOTAL_SEARCH_RESULTS = await totalSearchResults()
const searchResults = results.slice(0, TOTAL_SEARCH_RESULTS)
const isSimpleMode = await getIsSimpleInternetSearch()
if (isSimpleMode) {
await getOllamaURL()
return searchResults.map((result) => {
return {
url: result.link,
content: result.content
}
})
}
const docs: Document<Record<string, any>>[] = []
try {
for (const result of searchResults) {
const loader = new PageAssistHtmlLoader({
html: "",
url: result.link
})
const documents = await loader.loadByURL()
documents.forEach((doc) => {
docs.push(doc)
})
}
} catch (error) {
console.error(error)
}
const ollamaUrl = await getOllamaURL()
const embeddingModel = await defaultEmbeddingModelForRag()
const ollamaEmbedding = await pageAssistEmbeddingModel({
model: embeddingModel || "",
baseUrl: cleanUrl(ollamaUrl)
})
const chunkSize = await defaultEmbeddingChunkSize()
const chunkOverlap = await defaultEmbeddingChunkOverlap()
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize,
chunkOverlap
})
const chunks = await textSplitter.splitDocuments(docs)
const store = new MemoryVectorStore(ollamaEmbedding)
await store.addDocuments(chunks)
const resultsWithEmbeddings = await store.similaritySearch(query, 3)
const searchResult = resultsWithEmbeddings.map((result) => {
return {
url: result.metadata.url,
content: result.pageContent
}
})
return searchResult
}
const apiBraveSearch = async (braveApiKey: string, query: string) => {
const TOTAL_SEARCH_RESULTS = await totalSearchResults()
const searchURL = `https://api.search.brave.com/res/v1/web/search?q=${query}&count=${TOTAL_SEARCH_RESULTS}`
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 20000)
try {
const response = await fetch(searchURL, {
signal: abortController.signal,
headers: {
"X-Subscription-Token": braveApiKey,
Accept: "application/json",
}
})
if (!response.ok) {
return []
}
const data = await response.json() as BraveAPIResponse
return data?.web?.results.map(result => ({
title: result.title,
link: result.url,
content: result.description
}))
} catch (error) {
console.error('Brave API search failed:', error)
return []
}
}

View File

@ -6,6 +6,7 @@ import { webSogouSearch } from "./search-engines/sogou"
import { webBraveSearch } from "./search-engines/brave"
import { getWebsiteFromQuery, processSingleWebsite } from "./website"
import { searxngSearch } from "./search-engines/searxng"
import { braveAPISearch } from "./search-engines/brave-api"
const getHostName = (url: string) => {
try {
@ -26,6 +27,8 @@ const searchWeb = (provider: string, query: string) => {
return webBraveSearch(query)
case "searxng":
return searxngSearch(query)
case "brave-api":
return braveAPISearch(query)
default:
return webGoogleSearch(query)
}