Added Web search DuckDuckGo

This commit is contained in:
n4ze3m 2024-03-31 15:57:56 +05:30
parent 317011a6d2
commit 9ced6469ce
14 changed files with 455 additions and 112 deletions

View File

@ -28,6 +28,7 @@
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"antd": "^5.13.3", "antd": "^5.13.3",
"axios": "^1.6.7", "axios": "^1.6.7",
"cheerio": "^1.0.0-rc.12",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"i18next": "^23.10.1", "i18next": "^23.10.1",

View File

@ -1,8 +1,8 @@
{ {
"generalSettings": { "generalSettings": {
"title": "General Settings", "title": "General Settings",
"heading": "Web UI Settings",
"settings": { "settings": {
"heading": "Web UI Settings",
"speechRecognitionLang": { "speechRecognitionLang": {
"label": "Speech Recognition Language", "label": "Speech Recognition Language",
"placeholder": "Select a language" "placeholder": "Select a language"
@ -18,14 +18,25 @@
"dark": "Dark" "dark": "Dark"
} }
}, },
"searchMode": {
"label": "Perform Simple Internet Search"
},
"deleteChatHistory": { "deleteChatHistory": {
"label": "Delete Chat History", "label": "Delete Chat History",
"button": "Delete", "button": "Delete",
"confirm": "Are you sure you want to delete your chat history? This action cannot be undone." "confirm": "Are you sure you want to delete your chat history? This action cannot be undone."
} }
},
"webSearch": {
"heading": "Manage Web Search",
"searchMode": {
"label": "Perform Simple Internet Search"
},
"provider": {
"label": "Search Engine",
"placeholder": "Select a search engine"
},
"totalSearchResults": {
"label": "Total Search Results",
"placeholder": "Enter Total Search Results"
}
} }
}, },
"manageModels": { "manageModels": {
@ -205,7 +216,7 @@
} }
}, },
"manageSearch": { "manageSearch": {
"title": "Manage Web Search", "title": "Manage Web Search",
"heading": "Configure Web Search" "heading": "Configure Web Search"
} }
} }

View File

@ -1,8 +1,8 @@
{ {
"generalSettings": { "generalSettings": {
"title": "一般設定", "title": "一般設定",
"heading": "Web UIの設定",
"settings": { "settings": {
"heading": "Web UIの設定",
"speechRecognitionLang": { "speechRecognitionLang": {
"label": "音声認識の言語", "label": "音声認識の言語",
"placeholder": "言語を選択" "placeholder": "言語を選択"
@ -26,7 +26,21 @@
"button": "削除", "button": "削除",
"confirm": "チャット履歴を削除してもよろしいですか?この操作は元に戻せません。" "confirm": "チャット履歴を削除してもよろしいですか?この操作は元に戻せません。"
} }
} },
"webSearch": {
"heading": "ウェブ検索を管理する",
"searchMode": {
"label": "簡単なインターネット検索を実行する"
},
"provider": {
"label": "検索エンジン",
"placeholder": "検索エンジンを選択する"
},
"totalSearchResults": {
"label": "合計検索結果",
"placeholder": "合計検索結果を入力する"
}
}
}, },
"manageModels": { "manageModels": {
"title": "モデルを管理", "title": "モデルを管理",

View File

@ -1,8 +1,8 @@
{ {
"generalSettings": { "generalSettings": {
"title": "പൊതുവായ സെറ്റിംഗുകൾ", "title": "പൊതുവായ സെറ്റിംഗുകൾ",
"heading": "വെബ് UI സെറ്റിംഗുകൾ",
"settings": { "settings": {
"heading": "വെബ് UI സെറ്റിംഗുകൾ",
"speechRecognitionLang": { "speechRecognitionLang": {
"label": "സംഭാഷണ തിരിച്ചറിയല്‍ ഭാഷ", "label": "സംഭാഷണ തിരിച്ചറിയല്‍ ഭാഷ",
"placeholder": "ഒരു ഭാഷ തിരഞ്ഞെടുക്കുക" "placeholder": "ഒരു ഭാഷ തിരഞ്ഞെടുക്കുക"
@ -26,6 +26,20 @@
"button": "ഇല്ലാതാക്കുക", "button": "ഇല്ലാതാക്കുക",
"confirm": "നിങ്ങളുടെ ചാറ്റ് ചരിത്രം ഇല്ലാതാക്കണമെന്ന് തീർച്ചയാണോ? ഈ പ്രവർത്തനം പിന്നീട് പിൻവലിക്കാനാകില്ല." "confirm": "നിങ്ങളുടെ ചാറ്റ് ചരിത്രം ഇല്ലാതാക്കണമെന്ന് തീർച്ചയാണോ? ഈ പ്രവർത്തനം പിന്നീട് പിൻവലിക്കാനാകില്ല."
} }
},
"webSearch": {
"heading": "വെബ്ബ് തിരച്ചിൽ നിയന്ത്രിക്കുക",
"searchMode": {
"label": "സരളമായ ഇന്റർനെറ്റ് തിരച്ചിൽ നടത്തുക"
},
"provider": {
"label": "തിരച്ചിൽ എഞ്ചിൻ",
"placeholder": "തിരച്ചിൽ എഞ്ചിൻ തിരഞ്ഞെടുക്കുക"
},
"totalSearchResults": {
"label": "ആകെ തിരച്ചിൽ ഫലങ്ങൾ",
"placeholder": "ആകെ തിരച്ചിൽ ഫലങ്ങളുടെ എണ്ണം നൽകുക"
}
} }
}, },
"manageModels": { "manageModels": {

View File

@ -1,8 +1,8 @@
{ {
"generalSettings": { "generalSettings": {
"title": "一般设置", "title": "一般设置",
"heading": "Web UI 设置",
"settings": { "settings": {
"heading": "Web UI 设置",
"speechRecognitionLang": { "speechRecognitionLang": {
"label": "语音识别语言", "label": "语音识别语言",
"placeholder": "选择一种语言" "placeholder": "选择一种语言"
@ -26,7 +26,21 @@
"button": "删除", "button": "删除",
"confirm": "您确定要删除聊天历史记录吗?这个操作不能撤销。" "confirm": "您确定要删除聊天历史记录吗?这个操作不能撤销。"
} }
} },
"webSearch": {
"heading": "管理网络搜索",
"searchMode": {
"label": "执行简单的网际网路搜索"
},
"provider": {
"label": "搜索引擎",
"placeholder": "选择一个搜索引擎"
},
"totalSearchResults": {
"label": "总搜索结果",
"placeholder": "输入总搜索结果"
}
}
}, },
"manageModels": { "manageModels": {
"title": "管理模型", "title": "管理模型",

View File

@ -17,18 +17,13 @@ export const SettingOther = () => {
const { mode, toggleDarkMode } = useDarkMode() const { mode, toggleDarkMode } = useDarkMode()
const { t } = useTranslation("settings") const { t } = useTranslation("settings")
const { const { changeLocale, locale, supportLanguage } = useI18n()
changeLocale,
locale,
supportLanguage
}= useI18n()
return ( return (
<dl className="flex flex-col space-y-6 text-sm"> <dl className="flex flex-col space-y-6 text-sm">
<div> <div>
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white"> <h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
{t("generalSettings.heading")} {t("generalSettings.settings.heading")}
</h2> </h2>
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div> <div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div>
</div> </div>
@ -38,7 +33,9 @@ export const SettingOther = () => {
</span> </span>
<Select <Select
placeholder={t("generalSettings.settings.speechRecognitionLang.placeholder")} placeholder={t(
"generalSettings.settings.speechRecognitionLang.placeholder"
)}
allowClear allowClear
showSearch showSearch
options={SUPPORTED_LANGUAGES} options={SUPPORTED_LANGUAGES}
@ -86,7 +83,9 @@ export const SettingOther = () => {
) : ( ) : (
<MoonIcon className="w-4 h-4 mr-2" /> <MoonIcon className="w-4 h-4 mr-2" />
)} )}
{mode === "dark" ? t("generalSettings.settings.darkMode.options.light") : t("generalSettings.settings.darkMode.options.dark")} {mode === "dark"
? t("generalSettings.settings.darkMode.options.light")
: t("generalSettings.settings.darkMode.options.dark")}
</button> </button>
</div> </div>
<SearchModeSettings /> <SearchModeSettings />

View File

@ -1,40 +1,92 @@
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, useQueryClient } from "@tanstack/react-query"
import { Skeleton, Switch } from "antd" import { Select, Skeleton, Switch, InputNumber } from "antd"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import {
getIsSimpleInternetSearch,
setIsSimpleInternetSearch
} from "~/services/ollama"
export const SearchModeSettings = () => { export const SearchModeSettings = () => {
const { t } = useTranslation("settings") const { t } = useTranslation("settings")
const queryClient = useQueryClient()
const { data, status } = useQuery({
queryKey: ["fetchIsSimpleInternetSearch"], const form = useForm({
queryFn: () => getIsSimpleInternetSearch() initialValues: {
isSimpleInternetSearch: false,
searchProvider: "",
totalSearchResults: 0
}
}) })
const queryClient = useQueryClient() const { status } = useQuery({
queryKey: ["fetchSearchSettings"],
queryFn: async () => {
const data = await getSearchSettings()
form.setValues(data)
return data
}
})
if (status === "pending" || status === "error") { if (status === "pending" || status === "error") {
return <Skeleton active /> return <Skeleton active />
} }
return ( return (
<div className="flex flex-row justify-between"> <div>
<span className="text-gray-500 dark:text-neutral-50 "> <div className="mb-5">
{t("generalSettings.settings.searchMode.label")} <h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
</span> {t("generalSettings.webSearch.heading")}
</h2>
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3"></div>
</div>
<form
onSubmit={form.onSubmit(async (values) => {
await setSearchSettings(values)
})}
className="space-y-4">
<div className="flex flex-row justify-between">
<span className="text-gray-500 dark:text-neutral-50 ">
{t("generalSettings.webSearch.provider.label")}
</span>
<Select
placeholder={t("generalSettings.webSearch.provider.placeholder")}
showSearch
style={{ width: "200px" }}
options={SUPPORTED_SERACH_PROVIDERS}
filterOption={(input, option) =>
option!.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 ||
option!.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
{...form.getInputProps("searchProvider")}
/>
</div>
<div className="flex flex-row justify-between">
<span className="text-gray-500 dark:text-neutral-50 ">
{t("generalSettings.webSearch.searchMode.label")}
</span>
<Switch
{...form.getInputProps("isSimpleInternetSearch", {
type: "checkbox"
})}
/>
</div>
<div className="flex flex-row justify-between">
<span className="text-gray-500 dark:text-neutral-50 ">
{t("generalSettings.webSearch.totalSearchResults.label")}
</span>
<InputNumber
placeholder={t(
"generalSettings.webSearch.totalSearchResults.placeholder"
)}
{...form.getInputProps("totalSearchResults")}
style={{ width: "200px" }}
/>
</div>
<Switch <div className="flex justify-end">
checked={data} <SaveButton btnType="submit" />
onChange={(checked) => { </div>
setIsSimpleInternetSearch(checked) </form>
queryClient.invalidateQueries({
queryKey: ["fetchIsSimpleInternetSearch"]
})
}}
/>
</div> </div>
) )
} }

View File

@ -296,19 +296,6 @@ export const setWebPrompts = async (prompt: string, followUpPrompt: string) => {
await setWebSearchFollowUpPrompt(followUpPrompt) await setWebSearchFollowUpPrompt(followUpPrompt)
} }
export const getIsSimpleInternetSearch = async () => {
const isSimpleInternetSearch = await storage.get("isSimpleInternetSearch")
if (!isSimpleInternetSearch || isSimpleInternetSearch.length === 0) {
return true
}
return isSimpleInternetSearch === "true"
}
export const setIsSimpleInternetSearch = async (isSimpleInternetSearch: boolean) => {
await storage.set("isSimpleInternetSearch", isSimpleInternetSearch.toString())
}
export const getPageShareUrl = async () => { export const getPageShareUrl = async () => {
const pageShareUrl = await storage.get("pageShareUrl") const pageShareUrl = await storage.get("pageShareUrl")

79
src/services/search.ts Normal file
View File

@ -0,0 +1,79 @@
import { Storage } from "@plasmohq/storage"
const storage = new Storage()
const TOTAL_SEARCH_RESULTS = 2
const DEFAULT_PROVIDER = "google"
const AVAILABLE_PROVIDERS = ["google", "duckduckgo"] as const
export const getIsSimpleInternetSearch = async () => {
const isSimpleInternetSearch = await storage.get("isSimpleInternetSearch")
if (!isSimpleInternetSearch || isSimpleInternetSearch.length === 0) {
return true
}
return isSimpleInternetSearch === "true"
}
export const setIsSimpleInternetSearch = async (
isSimpleInternetSearch: boolean
) => {
await storage.set("isSimpleInternetSearch", isSimpleInternetSearch.toString())
}
export const getSearchProvider = async (): Promise<
(typeof AVAILABLE_PROVIDERS)[number]
> => {
const searchProvider = await storage.get("searchProvider")
if (!searchProvider || searchProvider.length === 0) {
return DEFAULT_PROVIDER
}
return searchProvider as (typeof AVAILABLE_PROVIDERS)[number]
}
export const setSearchProvider = async (searchProvider: string) => {
await storage.set("searchProvider", searchProvider)
}
export const totalSearchResults = async () => {
const totalSearchResults = await storage.get("totalSearchResults")
if (!totalSearchResults || totalSearchResults.length === 0) {
return TOTAL_SEARCH_RESULTS
}
return parseInt(totalSearchResults)
}
export const setTotalSearchResults = async (totalSearchResults: number) => {
await storage.set("totalSearchResults", totalSearchResults.toString())
}
export const getSearchSettings = async () => {
const [isSimpleInternetSearch, searchProvider, totalSearchResult] =
await Promise.all([
getIsSimpleInternetSearch(),
getSearchProvider(),
totalSearchResults()
])
return {
isSimpleInternetSearch,
searchProvider,
totalSearchResults: totalSearchResult
}
}
export const setSearchSettings = async ({
isSimpleInternetSearch,
searchProvider,
totalSearchResults
}: {
isSimpleInternetSearch: boolean
searchProvider: string
totalSearchResults: number
}) => {
await Promise.all([
setIsSimpleInternetSearch(isSimpleInternetSearch),
setSearchProvider(searchProvider),
setTotalSearchResults(totalSearchResults)
])
}

View File

@ -0,0 +1,10 @@
export const SUPPORTED_SERACH_PROVIDERS = [
{
label: "Google",
value: "google"
},
{
label: "DuckDuckGo",
value: "duckduckgo"
}
]

114
src/web/local-duckduckgo.ts Normal file
View File

@ -0,0 +1,114 @@
import { cleanUrl } from "@/libs/clean-url"
import { chromeRunTime } from "@/libs/runtime"
import { PageAssistHtmlLoader } from "@/loader/html"
import {
defaultEmbeddingChunkOverlap,
defaultEmbeddingChunkSize,
defaultEmbeddingModelForRag,
getOllamaURL
} from "@/services/ollama"
import {
getIsSimpleInternetSearch,
totalSearchResults
} from "@/services/search"
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama"
import type { Document } from "@langchain/core/documents"
import * as cheerio from "cheerio"
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
import { MemoryVectorStore } from "langchain/vectorstores/memory"
export const localDuckDuckGoSearch = async (query: string) => {
await chromeRunTime(cleanUrl("https://html.duckduckgo.com/html/?q=" + query))
const abortController = new AbortController()
setTimeout(() => abortController.abort(), 10000)
const htmlString = await fetch(
"https://html.duckduckgo.com/html/?q=" + query,
{
signal: abortController.signal
}
)
.then((response) => response.text())
.catch()
const $ = cheerio.load(htmlString)
const searchResults = Array.from($("div.results_links_deep")).map(
(result) => {
const title = $(result).find("a.result__a").text()
const link = $(result)
.find("a.result__snippet")
.attr("href")
.replace("//duckduckgo.com/l/?uddg=", "")
const content = $(result).find("a.result__snippet").text()
const decodedLink = decodeURIComponent(link)
return { title, link: decodedLink, content }
}
)
return searchResults
}
export const webDuckDuckGoSearch = async (query: string) => {
const results = await localDuckDuckGoSearch(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>>[] = []
for (const result of searchResults) {
const loader = new PageAssistHtmlLoader({
html: "",
url: result.link
})
const documents = await loader.loadByURL()
documents.forEach((doc) => {
docs.push(doc)
})
}
const ollamaUrl = await getOllamaURL()
const embeddingModle = await defaultEmbeddingModelForRag()
const ollamaEmbedding = new OllamaEmbeddings({
model: embeddingModle || "",
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
}

View File

@ -1,3 +1,7 @@
import {
getIsSimpleInternetSearch,
totalSearchResults
} from "@/services/search"
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama" import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama"
import type { Document } from "@langchain/core/documents" import type { Document } from "@langchain/core/documents"
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter" import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
@ -5,16 +9,13 @@ import { MemoryVectorStore } from "langchain/vectorstores/memory"
import { cleanUrl } from "~/libs/clean-url" import { cleanUrl } from "~/libs/clean-url"
import { chromeRunTime } from "~/libs/runtime" import { chromeRunTime } from "~/libs/runtime"
import { PageAssistHtmlLoader } from "~/loader/html" import { PageAssistHtmlLoader } from "~/loader/html"
import { defaultEmbeddingChunkOverlap, defaultEmbeddingChunkSize, defaultEmbeddingModelForRag, getIsSimpleInternetSearch, getOllamaURL } from "~/services/ollama" import {
defaultEmbeddingChunkOverlap,
defaultEmbeddingChunkSize,
defaultEmbeddingModelForRag,
getOllamaURL
} from "~/services/ollama"
const BLOCKED_HOSTS = [
"google.com",
"youtube.com",
"twitter.com",
"linkedin.com",
]
const TOTAL_SEARCH_RESULTS = 2
export const localGoogleSearch = async (query: string) => { export const localGoogleSearch = async (query: string) => {
await chromeRunTime( await chromeRunTime(
@ -40,23 +41,18 @@ export const localGoogleSearch = async (query: string) => {
(result) => { (result) => {
const title = result.querySelector("h3")?.textContent const title = result.querySelector("h3")?.textContent
const link = result.querySelector("a")?.getAttribute("href") const link = result.querySelector("a")?.getAttribute("href")
const content = Array.from(result.querySelectorAll("span")).map((span) => span.textContent).join(" ") const content = Array.from(result.querySelectorAll("span"))
.map((span) => span.textContent)
.join(" ")
return { title, link, content } return { title, link, content }
} }
) )
const filteredSearchResults = searchResults return searchResults
.filter(
(result) =>
!result.link ||
!BLOCKED_HOSTS.some((host) => result.link.includes(host))
)
.filter((result) => result.title && result.link)
return filteredSearchResults
} }
export const webGoogleSearch = async (query: string) => {
export const webSearch = async (query: string) => {
const results = await localGoogleSearch(query) const results = await localGoogleSearch(query)
const TOTAL_SEARCH_RESULTS = await totalSearchResults()
const searchResults = results.slice(0, TOTAL_SEARCH_RESULTS) const searchResults = results.slice(0, TOTAL_SEARCH_RESULTS)
const isSimpleMode = await getIsSimpleInternetSearch() const isSimpleMode = await getIsSimpleInternetSearch()
@ -71,7 +67,7 @@ export const webSearch = async (query: string) => {
}) })
} }
const docs: Document<Record<string, any>>[] = []; const docs: Document<Record<string, any>>[] = []
for (const result of searchResults) { for (const result of searchResults) {
const loader = new PageAssistHtmlLoader({ const loader = new PageAssistHtmlLoader({
html: "", html: "",
@ -89,14 +85,14 @@ export const webSearch = async (query: string) => {
const embeddingModle = await defaultEmbeddingModelForRag() const embeddingModle = await defaultEmbeddingModelForRag()
const ollamaEmbedding = new OllamaEmbeddings({ const ollamaEmbedding = new OllamaEmbeddings({
model: embeddingModle || "", model: embeddingModle || "",
baseUrl: cleanUrl(ollamaUrl), baseUrl: cleanUrl(ollamaUrl)
}) })
const chunkSize = await defaultEmbeddingChunkSize(); const chunkSize = await defaultEmbeddingChunkSize()
const chunkOverlap = await defaultEmbeddingChunkOverlap(); const chunkOverlap = await defaultEmbeddingChunkOverlap()
const textSplitter = new RecursiveCharacterTextSplitter({ const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize, chunkSize,
chunkOverlap, chunkOverlap
}) })
const chunks = await textSplitter.splitDocuments(docs) const chunks = await textSplitter.splitDocuments(docs)
@ -105,7 +101,6 @@ export const webSearch = async (query: string) => {
await store.addDocuments(chunks) await store.addDocuments(chunks)
const resultsWithEmbeddings = await store.similaritySearch(query, 3) const resultsWithEmbeddings = await store.similaritySearch(query, 3)
const searchResult = resultsWithEmbeddings.map((result) => { const searchResult = resultsWithEmbeddings.map((result) => {
@ -116,4 +111,4 @@ export const webSearch = async (query: string) => {
}) })
return searchResult return searchResult
} }

View File

@ -1,42 +1,61 @@
import { getWebSearchPrompt } from "~/services/ollama" import { getWebSearchPrompt } from "~/services/ollama"
import { webSearch } from "./local-google" import { webGoogleSearch } from "./local-google"
import { webDuckDuckGoSearch } from "./local-duckduckgo"
import { getSearchProvider } from "@/services/search"
const getHostName = (url: string) => { const getHostName = (url: string) => {
try { try {
const hostname = new URL(url).hostname const hostname = new URL(url).hostname
return hostname return hostname
} catch (e) { } catch (e) {
return "" return ""
} }
}
const searchWeb = (provider: string, query: string) => {
switch (provider) {
case "duckduckgo":
return webDuckDuckGoSearch(query)
default:
return webGoogleSearch(query)
}
} }
export const getSystemPromptForWeb = async (query: string) => { export const getSystemPromptForWeb = async (query: string) => {
try { try {
const search = await webSearch(query) const searchProvider = await getSearchProvider()
const search = await searchWeb(searchProvider, query)
const search_results = search.map((result, idx) => `<result source="${result.url}" id="${idx}">${result.content}</result>`).join("\n") const search_results = search
.map(
(result, idx) =>
`<result source="${result.url}" id="${idx}">${result.content}</result>`
)
.join("\n")
const current_date_time = new Date().toLocaleString() const current_date_time = new Date().toLocaleString()
const system = await getWebSearchPrompt(); const system = await getWebSearchPrompt()
const prompt = system.replace("{current_date_time}", current_date_time).replace("{search_results}", search_results) const prompt = system
.replace("{current_date_time}", current_date_time)
.replace("{search_results}", search_results)
return {
prompt,
source: search.map((result) => {
return { return {
prompt, url: result.url,
source: search.map((result) => { name: getHostName(result.url),
return { type: "url"
url: result.url,
name: getHostName(result.url),
type: "url",
}
})
}
} catch (e) {
console.error(e)
return {
prompt: "",
source: [],
} }
})
} }
} } catch (e) {
console.error(e)
return {
prompt: "",
source: []
}
}
}

View File

@ -1884,6 +1884,31 @@ charenc@0.0.2:
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
cheerio-select@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4"
integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==
dependencies:
boolbase "^1.0.0"
css-select "^5.1.0"
css-what "^6.1.0"
domelementtype "^2.3.0"
domhandler "^5.0.3"
domutils "^3.0.1"
cheerio@^1.0.0-rc.12:
version "1.0.0-rc.12"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683"
integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==
dependencies:
cheerio-select "^2.1.0"
dom-serializer "^2.0.0"
domhandler "^5.0.3"
domutils "^3.0.1"
htmlparser2 "^8.0.1"
parse5 "^7.0.0"
parse5-htmlparser2-tree-adapter "^7.0.0"
chokidar@^3.5.3, chokidar@^3.6.0: chokidar@^3.5.3, chokidar@^3.6.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
@ -3194,7 +3219,7 @@ html-to-text@^9.0.5:
htmlparser2 "^8.0.2" htmlparser2 "^8.0.2"
selderee "^0.11.0" selderee "^0.11.0"
htmlparser2@^8.0.2: htmlparser2@^8.0.1, htmlparser2@^8.0.2:
version "8.0.2" version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
@ -4998,6 +5023,14 @@ parse5-htmlparser2-tree-adapter@^6.0.0:
dependencies: dependencies:
parse5 "^6.0.1" parse5 "^6.0.1"
parse5-htmlparser2-tree-adapter@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1"
integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==
dependencies:
domhandler "^5.0.2"
parse5 "^7.0.0"
parse5@^5.1.1: parse5@^5.1.1:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
@ -5008,7 +5041,7 @@ parse5@^6.0.1:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
parse5@^7.1.1: parse5@^7.0.0, parse5@^7.1.1:
version "7.1.2" version "7.1.2"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
@ -6974,6 +7007,7 @@ winreg@0.0.12:
integrity sha512-typ/+JRmi7RqP1NanzFULK36vczznSNN8kWVA9vIqXyv8GhghUlwhGp1Xj3Nms1FsPcNnsQrJOR10N58/nQ9hQ== integrity sha512-typ/+JRmi7RqP1NanzFULK36vczznSNN8kWVA9vIqXyv8GhghUlwhGp1Xj3Nms1FsPcNnsQrJOR10N58/nQ9hQ==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
name wrap-ansi-cjs
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==