-
- {t("generalSettings.settings.deleteChatHistory.label")}
-
+
+
+
+ {t("generalSettings.system.heading")}
+
+
+
+
+
+ {t("generalSettings.system.deleteChatHistory.label")}
+
-
+
)
diff --git a/src/components/Option/Settings/search-mode.tsx b/src/components/Option/Settings/search-mode.tsx
index ee83e7e..ff17714 100644
--- a/src/components/Option/Settings/search-mode.tsx
+++ b/src/components/Option/Settings/search-mode.tsx
@@ -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 { Skeleton, Switch } from "antd"
+import { Select, Skeleton, Switch, InputNumber } from "antd"
import { useTranslation } from "react-i18next"
-import {
- getIsSimpleInternetSearch,
- setIsSimpleInternetSearch
-} from "~/services/ollama"
export const SearchModeSettings = () => {
const { t } = useTranslation("settings")
-
- const { data, status } = useQuery({
- queryKey: ["fetchIsSimpleInternetSearch"],
- queryFn: () => getIsSimpleInternetSearch()
+ const queryClient = useQueryClient()
+
+ const form = useForm({
+ 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") {
return
}
return (
-
-
- {t("generalSettings.settings.searchMode.label")}
-
+
+
+
+ {t("generalSettings.webSearch.heading")}
+
+
+
+
)
}
diff --git a/src/context/index.tsx b/src/context/index.tsx
new file mode 100644
index 0000000..7e91bae
--- /dev/null
+++ b/src/context/index.tsx
@@ -0,0 +1,26 @@
+import { Message } from "@/types/message"
+import React, { Dispatch, SetStateAction, createContext } from "react"
+
+interface PageAssistContext {
+ messages: Message[]
+ setMessages: Dispatch
>
+
+ controller: AbortController | null
+ setController: Dispatch>
+}
+
+export const PageAssistContext = createContext({
+ messages: [],
+ setMessages: () => {},
+
+ controller: null,
+ setController: () => {}
+})
+
+export const usePageAssist = () => {
+ const context = React.useContext(PageAssistContext)
+ if (!context) {
+ throw new Error("usePageAssist must be used within a PageAssistContext")
+ }
+ return context
+}
diff --git a/src/entries/background.ts b/src/entries/background.ts
index af5c1e3..d4f075b 100644
--- a/src/entries/background.ts
+++ b/src/entries/background.ts
@@ -81,7 +81,8 @@ export default defineBackground({
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
const tab = tabs[0]
chrome.sidePanel.open({
- tabId: tab.id!
+ // tabId: tab.id!,
+ windowId: tab.windowId!,
})
})
} else if (message.type === "pull_model") {
@@ -113,7 +114,7 @@ export default defineBackground({
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
const tab = tabs[0]
chrome.sidePanel.open({
- tabId: tab.id!
+ windowId: tab.windowId!
})
})
break
@@ -133,7 +134,7 @@ export default defineBackground({
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
const tab = tabs[0]
await chrome.sidePanel.open({
- tabId: tab.id!
+ windowId: tab.windowId!,
})
})
}
diff --git a/src/entries/options/App.tsx b/src/entries/options/App.tsx
index b0221ef..f78a803 100644
--- a/src/entries/options/App.tsx
+++ b/src/entries/options/App.tsx
@@ -1,7 +1,5 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { MemoryRouter } from "react-router-dom"
-import { ToastContainer } from "react-toastify"
-import "react-toastify/dist/ReactToastify.css"
const queryClient = new QueryClient()
import { ConfigProvider, Empty, theme } from "antd"
import { StyleProvider } from "@ant-design/cssinjs"
@@ -9,6 +7,7 @@ import { useDarkMode } from "~/hooks/useDarkmode"
import { OptionRouting } from "~/routes"
import "~/i18n"
import { useTranslation } from "react-i18next"
+import { PageAssistProvider } from "@/components/Common/PageAssistProvider"
function IndexOption() {
const { mode } = useDarkMode()
@@ -27,12 +26,12 @@ function IndexOption() {
}}
description={t("common:noData")}
/>
- )}
- >
+ )}>
-
-
+
+
+
diff --git a/src/entries/sidepanel/App.tsx b/src/entries/sidepanel/App.tsx
index 26ce815..495d6fd 100644
--- a/src/entries/sidepanel/App.tsx
+++ b/src/entries/sidepanel/App.tsx
@@ -1,14 +1,13 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { MemoryRouter } from "react-router-dom"
import { SidepanelRouting } from "~/routes"
-import { ToastContainer } from "react-toastify"
-import "react-toastify/dist/ReactToastify.css"
const queryClient = new QueryClient()
import { ConfigProvider, Empty, theme } from "antd"
import { StyleProvider } from "@ant-design/cssinjs"
import { useDarkMode } from "~/hooks/useDarkmode"
import "~/i18n"
import { useTranslation } from "react-i18next"
+import { PageAssistProvider } from "@/components/Common/PageAssistProvider"
function IndexSidepanel() {
const { mode } = useDarkMode()
@@ -28,12 +27,12 @@ function IndexSidepanel() {
}}
description={t("common:noData")}
/>
- )}
- >
+ )}>
-
-
+
+
+
diff --git a/src/hooks/chat-helper/index.ts b/src/hooks/chat-helper/index.ts
new file mode 100644
index 0000000..51d320c
--- /dev/null
+++ b/src/hooks/chat-helper/index.ts
@@ -0,0 +1,160 @@
+import { saveHistory, saveMessage } from "@/libs/db"
+import { ChatHistory } from "@/store/option"
+
+export const saveMessageOnError = async ({
+ e,
+ history,
+ setHistory,
+ image,
+ userMessage,
+ botMessage,
+ historyId,
+ selectedModel,
+ setHistoryId,
+ isRegenerating
+}: {
+ e: any
+ setHistory: (history: ChatHistory) => void
+ history: ChatHistory
+ userMessage: string
+ image: string
+ botMessage: string
+ historyId: string | null
+ selectedModel: string
+ setHistoryId: (historyId: string) => void
+ isRegenerating: boolean
+}) => {
+ if (
+ e?.name === "AbortError" ||
+ e?.message === "AbortError" ||
+ e?.name?.includes("AbortError") ||
+ e?.message?.includes("AbortError")
+ ) {
+ setHistory([
+ ...history,
+ {
+ role: "user",
+ content: userMessage,
+ image
+ },
+ {
+ role: "assistant",
+ content: botMessage
+ }
+ ])
+
+ if (historyId) {
+ if (!isRegenerating) {
+ await saveMessage(
+ historyId,
+ selectedModel,
+ "user",
+ userMessage,
+ [image],
+ [],
+ 1
+ )
+ }
+ await saveMessage(
+ historyId,
+ selectedModel,
+ "assistant",
+ botMessage,
+ [],
+ [],
+ 2
+ )
+ } else {
+ const newHistoryId = await saveHistory(userMessage)
+ if (!isRegenerating) {
+ await saveMessage(
+ newHistoryId.id,
+ selectedModel,
+ "user",
+ userMessage,
+ [image],
+ [],
+ 1
+ )
+ }
+ await saveMessage(
+ newHistoryId.id,
+ selectedModel,
+ "assistant",
+ botMessage,
+ [],
+ [],
+ 2
+ )
+ setHistoryId(newHistoryId.id)
+ }
+
+ return true
+ }
+
+ return false
+}
+
+export const saveMessageOnSuccess = async ({
+ historyId,
+ setHistoryId,
+ isRegenerate,
+ selectedModel,
+ message,
+ image,
+ fullText,
+ source
+}: {
+ historyId: string | null
+ setHistoryId: (historyId: string) => void
+ isRegenerate: boolean
+ selectedModel: string | null
+ message: string
+ image: string
+ fullText: string
+ source: any[]
+}) => {
+ if (historyId) {
+ if (!isRegenerate) {
+ await saveMessage(
+ historyId,
+ selectedModel,
+ "user",
+ message,
+ [image],
+ [],
+ 1
+ )
+ }
+ await saveMessage(
+ historyId,
+ selectedModel!,
+ "assistant",
+ fullText,
+ [],
+ source,
+ 2
+ )
+ } else {
+ const newHistoryId = await saveHistory(message)
+ await saveMessage(
+ newHistoryId.id,
+ selectedModel,
+ "user",
+ message,
+ [image],
+ [],
+ 1
+ )
+ await saveMessage(
+ newHistoryId.id,
+ selectedModel!,
+ "assistant",
+ fullText,
+ [],
+ source,
+ 2
+ )
+ setHistoryId(newHistoryId.id)
+ }
+}
diff --git a/src/hooks/useMessageOption.tsx b/src/hooks/useMessageOption.tsx
index d5e49b1..f0b0d63 100644
--- a/src/hooks/useMessageOption.tsx
+++ b/src/hooks/useMessageOption.tsx
@@ -11,10 +11,9 @@ import { HumanMessage, SystemMessage } from "@langchain/core/messages"
import { useStoreMessageOption } from "~/store/option"
import {
deleteChatForEdit,
+ generateID,
getPromptById,
removeMessageUsingHistoryId,
- saveHistory,
- saveMessage,
updateMessageByIndex
} from "~/libs/db"
import { useNavigate } from "react-router-dom"
@@ -22,13 +21,19 @@ import { notification } from "antd"
import { getSystemPromptForWeb } from "~/web/web"
import { generateHistory } from "@/utils/generate-history"
import { useTranslation } from "react-i18next"
+import { saveMessageOnError, saveMessageOnSuccess } from "./chat-helper"
+import { usePageAssist } from "@/context"
export const useMessageOption = () => {
const {
- history,
+ controller: abortController,
+ setController: setAbortController,
messages,
+ setMessages
+ } = usePageAssist()
+ const {
+ history,
setHistory,
- setMessages,
setStreaming,
streaming,
setIsFirstMessage,
@@ -59,8 +64,6 @@ export const useMessageOption = () => {
const navigate = useNavigate()
const textareaRef = React.useRef(null)
- const abortControllerRef = React.useRef(null)
-
const clearChat = () => {
navigate("/")
setMessages([])
@@ -78,14 +81,14 @@ export const useMessageOption = () => {
image: string,
isRegenerate: boolean,
messages: Message[],
- history: ChatHistory
+ history: ChatHistory,
+ signal: AbortSignal
) => {
const url = await getOllamaURL()
if (image.length > 0) {
image = `data:image/jpeg;base64,${image.split(",")[1]}`
}
- abortControllerRef.current = new AbortController()
const ollama = new ChatOllama({
model: selectedModel!,
@@ -93,6 +96,8 @@ export const useMessageOption = () => {
})
let newMessage: Message[] = []
+ let generateMessageId = generateID()
+
if (!isRegenerate) {
newMessage = [
...messages,
@@ -107,7 +112,8 @@ export const useMessageOption = () => {
isBot: true,
name: selectedModel,
message: "▋",
- sources: []
+ sources: [],
+ id: generateMessageId
}
]
} else {
@@ -117,12 +123,14 @@ export const useMessageOption = () => {
isBot: true,
name: selectedModel,
message: "▋",
- sources: []
+ sources: [],
+ id: generateMessageId
}
]
}
setMessages(newMessage)
- const appendingIndex = newMessage.length - 1
+ let fullText = ""
+ let contentToSave = ""
try {
setIsSearchingInternet(true)
@@ -195,138 +203,93 @@ export const useMessageOption = () => {
const chunks = await ollama.stream(
[...applicationChatHistory, humanMessage],
{
- signal: abortControllerRef.current.signal
+ signal: signal
}
)
let count = 0
for await (const chunk of chunks) {
+ contentToSave += chunk.content
+ fullText += chunk.content
if (count === 0) {
setIsProcessing(true)
- newMessage[appendingIndex].message = chunk.content + "▋"
- setMessages(newMessage)
- } else {
- newMessage[appendingIndex].message =
- newMessage[appendingIndex].message.slice(0, -1) +
- chunk.content +
- "▋"
- setMessages(newMessage)
}
-
+ setMessages((prev) => {
+ return prev.map((message) => {
+ if (message.id === generateMessageId) {
+ return {
+ ...message,
+ message: fullText.slice(0, -1) + "▋"
+ }
+ }
+ return message
+ })
+ })
count++
}
-
- newMessage[appendingIndex].message = newMessage[
- appendingIndex
- ].message.slice(0, -1)
-
- newMessage[appendingIndex].sources = source
-
- if (!isRegenerate) {
- setHistory([
- ...history,
- {
- role: "user",
- content: message,
- image
- },
- {
- role: "assistant",
- content: newMessage[appendingIndex].message
+ // update the message with the full text
+ setMessages((prev) => {
+ return prev.map((message) => {
+ if (message.id === generateMessageId) {
+ return {
+ ...message,
+ message: fullText,
+ sources: source
+ }
}
- ])
- } else {
- setHistory([
- ...history,
- {
- role: "assistant",
- content: newMessage[appendingIndex].message
- }
- ])
- }
+ return message
+ })
+ })
- if (historyId) {
- if (!isRegenerate) {
- await saveMessage(historyId, selectedModel!, "user", message, [image])
- }
- await saveMessage(
- historyId,
- selectedModel!,
- "assistant",
- newMessage[appendingIndex].message,
- [],
- source
- )
- } else {
- const newHistoryId = await saveHistory(message)
- await saveMessage(newHistoryId.id, selectedModel!, "user", message, [
+ setHistory([
+ ...history,
+ {
+ role: "user",
+ content: message,
image
- ])
- await saveMessage(
- newHistoryId.id,
- selectedModel!,
- "assistant",
- newMessage[appendingIndex].message,
- [],
- source
- )
- setHistoryId(newHistoryId.id)
- }
+ },
+ {
+ role: "assistant",
+ content: fullText
+ }
+ ])
+
+ await saveMessageOnSuccess({
+ historyId,
+ setHistoryId,
+ isRegenerate,
+ selectedModel: selectedModel,
+ message,
+ image,
+ fullText,
+ source
+ })
setIsProcessing(false)
setStreaming(false)
} catch (e) {
- //@ts-ignore
- if (e?.name === "AbortError") {
- newMessage[appendingIndex].message = newMessage[
- appendingIndex
- ].message.slice(0, -1)
+ const errorSave = await saveMessageOnError({
+ e,
+ botMessage: fullText,
+ history,
+ historyId,
+ image,
+ selectedModel,
+ setHistory,
+ setHistoryId,
+ userMessage: message,
+ isRegenerating: isRegenerate
+ })
- setHistory([
- ...history,
- {
- role: "user",
- content: message,
- image
- },
- {
- role: "assistant",
- content: newMessage[appendingIndex].message
- }
- ])
-
- if (historyId) {
- await saveMessage(historyId, selectedModel!, "user", message, [image])
- await saveMessage(
- historyId,
- selectedModel!,
- "assistant",
- newMessage[appendingIndex].message,
- []
- )
- } else {
- const newHistoryId = await saveHistory(message)
- await saveMessage(newHistoryId.id, selectedModel!, "user", message, [
- image
- ])
- await saveMessage(
- newHistoryId.id,
- selectedModel!,
- "assistant",
- newMessage[appendingIndex].message,
- []
- )
- setHistoryId(newHistoryId.id)
- }
- } else {
- //@ts-ignore
+ if (!errorSave) {
notification.error({
message: t("error"),
description: e?.message || t("somethingWentWrong")
})
}
-
setIsProcessing(false)
setStreaming(false)
+ } finally {
+ setAbortController(null)
}
}
@@ -335,14 +298,14 @@ export const useMessageOption = () => {
image: string,
isRegenerate: boolean,
messages: Message[],
- history: ChatHistory
+ history: ChatHistory,
+ signal: AbortSignal
) => {
const url = await getOllamaURL()
if (image.length > 0) {
image = `data:image/jpeg;base64,${image.split(",")[1]}`
}
- abortControllerRef.current = new AbortController()
const ollama = new ChatOllama({
model: selectedModel!,
@@ -350,6 +313,8 @@ export const useMessageOption = () => {
})
let newMessage: Message[] = []
+ let generateMessageId = generateID()
+
if (!isRegenerate) {
newMessage = [
...messages,
@@ -364,7 +329,8 @@ export const useMessageOption = () => {
isBot: true,
name: selectedModel,
message: "▋",
- sources: []
+ sources: [],
+ id: generateMessageId
}
]
} else {
@@ -374,12 +340,14 @@ export const useMessageOption = () => {
isBot: true,
name: selectedModel,
message: "▋",
- sources: []
+ sources: [],
+ id: generateMessageId
}
]
}
setMessages(newMessage)
- const appendingIndex = newMessage.length - 1
+ let fullText = ""
+ let contentToSave = ""
try {
const prompt = await systemPromptForNonRagOption()
@@ -441,132 +409,94 @@ export const useMessageOption = () => {
const chunks = await ollama.stream(
[...applicationChatHistory, humanMessage],
{
- signal: abortControllerRef.current.signal
+ signal: signal
}
)
let count = 0
for await (const chunk of chunks) {
+ contentToSave += chunk.content
+ fullText += chunk.content
if (count === 0) {
setIsProcessing(true)
- newMessage[appendingIndex].message = chunk.content + "▋"
- setMessages(newMessage)
- } else {
- newMessage[appendingIndex].message =
- newMessage[appendingIndex].message.slice(0, -1) +
- chunk.content +
- "▋"
- setMessages(newMessage)
}
-
+ setMessages((prev) => {
+ return prev.map((message) => {
+ if (message.id === generateMessageId) {
+ return {
+ ...message,
+ message: fullText.slice(0, -1) + "▋"
+ }
+ }
+ return message
+ })
+ })
count++
}
- newMessage[appendingIndex].message = newMessage[
- appendingIndex
- ].message.slice(0, -1)
-
- if (!isRegenerate) {
- setHistory([
- ...history,
- {
- role: "user",
- content: message,
- image
- },
- {
- role: "assistant",
- content: newMessage[appendingIndex].message
+ setMessages((prev) => {
+ return prev.map((message) => {
+ if (message.id === generateMessageId) {
+ return {
+ ...message,
+ message: fullText.slice(0, -1)
+ }
}
- ])
- } else {
- setHistory([
- ...history,
- {
- role: "assistant",
- content: newMessage[appendingIndex].message
- }
- ])
- }
+ return message
+ })
+ })
- if (historyId) {
- if (!isRegenerate) {
- await saveMessage(historyId, selectedModel, "user", message, [image])
- }
- await saveMessage(
- historyId,
- selectedModel,
- "assistant",
- newMessage[appendingIndex].message,
- []
- )
- } else {
- const newHistoryId = await saveHistory(message)
- await saveMessage(newHistoryId.id, selectedModel, "user", message, [
+ setHistory([
+ ...history,
+ {
+ role: "user",
+ content: message,
image
- ])
- await saveMessage(
- newHistoryId.id,
- selectedModel,
- "assistant",
- newMessage[appendingIndex].message,
- []
- )
- setHistoryId(newHistoryId.id)
- }
+ },
+ {
+ role: "assistant",
+ content: fullText
+ }
+ ])
+
+ await saveMessageOnSuccess({
+ historyId,
+ setHistoryId,
+ isRegenerate,
+ selectedModel: selectedModel,
+ message,
+ image,
+ fullText,
+ source: []
+ })
setIsProcessing(false)
setStreaming(false)
+ setIsProcessing(false)
+ setStreaming(false)
} catch (e) {
- if (e?.name === "AbortError") {
- newMessage[appendingIndex].message = newMessage[
- appendingIndex
- ].message.slice(0, -1)
+ const errorSave = await saveMessageOnError({
+ e,
+ botMessage: fullText,
+ history,
+ historyId,
+ image,
+ selectedModel,
+ setHistory,
+ setHistoryId,
+ userMessage: message,
+ isRegenerating: isRegenerate
+ })
- setHistory([
- ...history,
- {
- role: "user",
- content: message,
- image
- },
- {
- role: "assistant",
- content: newMessage[appendingIndex].message
- }
- ])
-
- if (historyId) {
- await saveMessage(historyId, selectedModel, "user", message, [image])
- await saveMessage(
- historyId,
- selectedModel,
- "assistant",
- newMessage[appendingIndex].message,
- []
- )
- } else {
- const newHistoryId = await saveHistory(message)
- await saveMessage(newHistoryId.id, selectedModel, "user", message, [
- image
- ])
- await saveMessage(
- newHistoryId.id,
- selectedModel,
- "assistant",
- newMessage[appendingIndex].message,
- []
- )
- setHistoryId(newHistoryId.id)
- }
- } else {
+ if (!errorSave) {
notification.error({
message: t("error"),
description: e?.message || t("somethingWentWrong")
})
}
-
setIsProcessing(false)
setStreaming(false)
+ } finally {
+ setAbortController(null)
}
}
@@ -575,22 +505,34 @@ export const useMessageOption = () => {
image,
isRegenerate = false,
messages: chatHistory,
- memory
+ memory,
+ controller
}: {
message: string
image: string
isRegenerate?: boolean
messages?: Message[]
memory?: ChatHistory
+ controller?: AbortController
}) => {
setStreaming(true)
+ let signal: AbortSignal
+ if (!controller) {
+ const newController = new AbortController()
+ signal = newController.signal
+ setAbortController(newController)
+ } else {
+ setAbortController(controller)
+ signal = controller.signal
+ }
if (webSearch) {
await searchChatMode(
message,
image,
isRegenerate,
chatHistory || messages,
- memory || history
+ memory || history,
+ signal
)
} else {
await normalChatMode(
@@ -598,7 +540,8 @@ export const useMessageOption = () => {
image,
isRegenerate,
chatHistory || messages,
- memory || history
+ memory || history,
+ signal
)
}
}
@@ -611,28 +554,29 @@ export const useMessageOption = () => {
}
if (history.length > 0) {
const lastMessage = history[history.length - 2]
- let newHistory = history
+ let newHistory = history.slice(0, -2)
let mewMessages = messages
- newHistory.pop()
mewMessages.pop()
setHistory(newHistory)
setMessages(mewMessages)
await removeMessageUsingHistoryId(historyId)
if (lastMessage.role === "user") {
+ const newController = new AbortController()
await onSubmit({
message: lastMessage.content,
image: lastMessage.image || "",
isRegenerate: true,
- memory: newHistory
+ memory: newHistory,
+ controller: newController
})
}
}
}
const stopStreamingRequest = () => {
- if (abortControllerRef.current) {
- abortControllerRef.current.abort()
- abortControllerRef.current = null
+ if (abortController) {
+ abortController.abort()
+ setAbortController(null)
}
}
@@ -653,7 +597,6 @@ export const useMessageOption = () => {
message: string,
isHuman: boolean
) => {
- // update message and history by index
let newMessages = messages
let newHistory = history
@@ -665,20 +608,21 @@ export const useMessageOption = () => {
}
const currentHumanMessage = newMessages[index]
- newMessages[index].message = message
- newHistory[index].content = message
+
const previousMessages = newMessages.slice(0, index + 1)
setMessages(previousMessages)
- const previousHistory = newHistory.slice(0, index + 1)
+ const previousHistory = newHistory.slice(0, index)
setHistory(previousHistory)
await updateMessageByIndex(historyId, index, message)
await deleteChatForEdit(historyId, index)
+ const abortController = new AbortController()
await onSubmit({
message: message,
image: currentHumanMessage.images[0] || "",
isRegenerate: true,
messages: previousMessages,
- memory: previousHistory
+ memory: previousHistory,
+ controller: abortController
})
} else {
newMessages[index].message = message
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index e85b63b..7c8820c 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -3,15 +3,20 @@ import { initReactI18next } from "react-i18next";
import { en } from "./lang/en";
import { ml } from "./lang/ml";
import { zh } from "./lang/zh";
+import { ja } from "./lang/ja";
+import LanguageDetector from 'i18next-browser-languagedetector';
i18n
+ .use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: en,
ml: ml,
"zh-CN": zh,
- zh: zh
+ zh: zh,
+ ja: ja,
+ "ja-JP": ja
},
fallbackLng: "en",
lng: localStorage.getItem("i18nextLng") || "en",
diff --git a/src/i18n/lang/ja.ts b/src/i18n/lang/ja.ts
new file mode 100644
index 0000000..2026585
--- /dev/null
+++ b/src/i18n/lang/ja.ts
@@ -0,0 +1,14 @@
+import option from "@/assets/locale/ja-JP/option.json";
+import playground from "@/assets/locale/ja-JP/playground.json";
+import common from "@/assets/locale/ja-JP/common.json";
+import sidepanel from "@/assets/locale/ja-JP/sidepanel.json";
+import settings from "@/assets/locale/ja-JP/settings.json";
+
+
+export const ja = {
+ option,
+ playground,
+ common,
+ sidepanel,
+ settings
+}
\ No newline at end of file
diff --git a/src/i18n/support-language.ts b/src/i18n/support-language.ts
index 04c1867..37a52ce 100644
--- a/src/i18n/support-language.ts
+++ b/src/i18n/support-language.ts
@@ -11,5 +11,9 @@ export const supportLanguage = [
{
label: "简体中文",
value: "zh-CN"
+ },
+ {
+ label: "日本語",
+ value: "ja-JP"
}
]
\ No newline at end of file
diff --git a/src/libs/db.ts b/src/libs/db.ts
index 095c865..bab011a 100644
--- a/src/libs/db.ts
+++ b/src/libs/db.ts
@@ -6,6 +6,7 @@ import {
type HistoryInfo = {
id: string
title: string
+ is_rag: boolean
createdAt: number
}
@@ -31,7 +32,6 @@ type Message = {
createdAt: number
}
-
type Webshare = {
id: string
title: string
@@ -41,7 +41,6 @@ type Webshare = {
createdAt: number
}
-
type Prompt = {
id: string
title: string
@@ -125,7 +124,6 @@ export class PageAssitDatabase {
await this.db.remove(history_id)
}
-
async getAllPrompts(): Promise {
return new Promise((resolve, reject) => {
this.db.get("prompts", (result) => {
@@ -146,7 +144,12 @@ export class PageAssitDatabase {
this.db.set({ prompts: newPrompts })
}
- async updatePrompt(id: string, title: string, content: string, is_system: boolean) {
+ async updatePrompt(
+ id: string,
+ title: string,
+ content: string,
+ is_system: boolean
+ ) {
const prompts = await this.getAllPrompts()
const newPrompts = prompts.map((prompt) => {
if (prompt.id === id) {
@@ -164,7 +167,6 @@ export class PageAssitDatabase {
return prompts.find((prompt) => prompt.id === id)
}
-
async getWebshare(id: string) {
return new Promise((resolve, reject) => {
this.db.get(id, (result) => {
@@ -173,7 +175,6 @@ export class PageAssitDatabase {
})
}
-
async getAllWebshares(): Promise {
return new Promise((resolve, reject) => {
this.db.get("webshares", (result) => {
@@ -207,18 +208,17 @@ export class PageAssitDatabase {
}
}
-
-const generateID = () => {
+export const generateID = () => {
return "pa_xxxx-xxxx-xxx-xxxx".replace(/[x]/g, () => {
const r = Math.floor(Math.random() * 16)
return r.toString(16)
})
}
-export const saveHistory = async (title: string) => {
+export const saveHistory = async (title: string, is_rag?: boolean) => {
const id = generateID()
const createdAt = Date.now()
- const history = { id, title, createdAt }
+ const history = { id, title, createdAt, is_rag }
const db = new PageAssitDatabase()
await db.addChatHistory(history)
return history
@@ -230,11 +230,24 @@ export const saveMessage = async (
role: string,
content: string,
images: string[],
- source?: any[]
+ source?: any[],
+ time?: number
) => {
const id = generateID()
- const createdAt = Date.now()
- const message = { id, history_id, name, role, content, images, createdAt, sources: source }
+ let createdAt = Date.now()
+ if (time) {
+ createdAt += time
+ }
+ const message = {
+ id,
+ history_id,
+ name,
+ role,
+ content,
+ images,
+ createdAt,
+ sources: source
+ }
const db = new PageAssitDatabase()
await db.addMessage(message)
return message
@@ -292,19 +305,20 @@ export const removeMessageUsingHistoryId = async (history_id: string) => {
await db.db.set({ [history_id]: chatHistory })
}
-
export const getAllPrompts = async () => {
const db = new PageAssitDatabase()
return await db.getAllPrompts()
}
-
-export const updateMessageByIndex = async (history_id: string, index: number, message: string) => {
+export const updateMessageByIndex = async (
+ history_id: string,
+ index: number,
+ message: string
+) => {
const db = new PageAssitDatabase()
const chatHistory = (await db.getChatHistory(history_id)).reverse()
chatHistory[index].content = message
await db.db.set({ [history_id]: chatHistory.reverse() })
-
}
export const deleteChatForEdit = async (history_id: string, index: number) => {
@@ -315,7 +329,15 @@ export const deleteChatForEdit = async (history_id: string, index: number) => {
await db.db.set({ [history_id]: previousHistory.reverse() })
}
-export const savePrompt = async ({ content, title, is_system = false }: { title: string, content: string, is_system: boolean }) => {
+export const savePrompt = async ({
+ content,
+ title,
+ is_system = false
+}: {
+ title: string
+ content: string
+ is_system: boolean
+}) => {
const db = new PageAssitDatabase()
const id = generateID()
const createdAt = Date.now()
@@ -324,21 +346,28 @@ export const savePrompt = async ({ content, title, is_system = false }: { title:
return prompt
}
-
export const deletePromptById = async (id: string) => {
const db = new PageAssitDatabase()
await db.deletePrompt(id)
return id
}
-
-export const updatePrompt = async ({ content, id, title, is_system }: { id: string, title: string, content: string, is_system: boolean }) => {
+export const updatePrompt = async ({
+ content,
+ id,
+ title,
+ is_system
+}: {
+ id: string
+ title: string
+ content: string
+ is_system: boolean
+}) => {
const db = new PageAssitDatabase()
await db.updatePrompt(id, title, content, is_system)
return id
}
-
export const getPromptById = async (id: string) => {
if (!id || id.trim() === "") return null
const db = new PageAssitDatabase()
@@ -354,10 +383,19 @@ export const deleteWebshare = async (id: string) => {
const db = new PageAssitDatabase()
await db.deleteWebshare(id)
return id
-
}
-export const saveWebshare = async ({ title, url, api_url, share_id }: { title: string, url: string, api_url: string, share_id: string }) => {
+export const saveWebshare = async ({
+ title,
+ url,
+ api_url,
+ share_id
+}: {
+ title: string
+ url: string
+ api_url: string
+ share_id: string
+}) => {
const db = new PageAssitDatabase()
const id = generateID()
const createdAt = Date.now()
@@ -368,7 +406,7 @@ export const saveWebshare = async ({ title, url, api_url, share_id }: { title: s
export const getUserId = async () => {
const db = new PageAssitDatabase()
- const id = await db.getUserID() as string
+ const id = (await db.getUserID()) as string
if (!id || id?.trim() === "") {
const user_id = "user_xxxx-xxxx-xxx-xxxx-xxxx".replace(/[x]/g, () => {
const r = Math.floor(Math.random() * 16)
@@ -378,4 +416,4 @@ export const getUserId = async () => {
return user_id
}
return id
-}
\ No newline at end of file
+}
diff --git a/src/public/_locales/ja/messages.json b/src/public/_locales/ja/messages.json
new file mode 100644
index 0000000..569ec1a
--- /dev/null
+++ b/src/public/_locales/ja/messages.json
@@ -0,0 +1,11 @@
+{
+ "extName": {
+ "message": "Page Assist - ローカルAIモデル用のWeb UI"
+ },
+ "extDescription": {
+ "message": "ローカルで実行中のAIモデルを使って、Webブラウジングをアシストします。"
+ },
+ "openSidePanelToChat": {
+ "message": "サイドパネルを開いてチャット"
+ }
+ }
\ No newline at end of file
diff --git a/src/public/_locales/zh-CN/messages.json b/src/public/_locales/zh_CN/messages.json
similarity index 99%
rename from src/public/_locales/zh-CN/messages.json
rename to src/public/_locales/zh_CN/messages.json
index f777bfb..9b4fefe 100644
--- a/src/public/_locales/zh-CN/messages.json
+++ b/src/public/_locales/zh_CN/messages.json
@@ -8,4 +8,4 @@
"openSidePanelToChat": {
"message": "打开侧边栏进行聊天"
}
-}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 87c6c4e..c474548 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -8,6 +8,8 @@ import { OptionPrompt } from "./option-settings-prompt"
import { OptionOllamaSettings } from "./options-settings-ollama"
import { OptionSettings } from "./option-settings"
import { OptionShare } from "./option-settings-share"
+import { OptionKnowledgeBase } from "./option-settings-knowledge"
+import { OptionAbout } from "./option-settings-about"
export const OptionRouting = () => {
const { mode } = useDarkMode()
@@ -21,6 +23,8 @@ export const OptionRouting = () => {
} />
} />
} />
+ } />
+ } />
)
diff --git a/src/routes/option-settings-about.tsx b/src/routes/option-settings-about.tsx
new file mode 100644
index 0000000..982f1ea
--- /dev/null
+++ b/src/routes/option-settings-about.tsx
@@ -0,0 +1,13 @@
+import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
+import OptionLayout from "~/components/Layouts/Layout"
+import { AboutApp } from "@/components/Option/Settings/about"
+
+export const OptionAbout = () => {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/src/routes/option-settings-knowledge.tsx b/src/routes/option-settings-knowledge.tsx
new file mode 100644
index 0000000..0d8c573
--- /dev/null
+++ b/src/routes/option-settings-knowledge.tsx
@@ -0,0 +1,12 @@
+import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
+import OptionLayout from "~/components/Layouts/Layout"
+
+export const OptionKnowledgeBase = () => {
+ return (
+
+
+ hey
+
+
+ )
+}
diff --git a/src/services/ollama.ts b/src/services/ollama.ts
index 9a02c48..e8e65b1 100644
--- a/src/services/ollama.ts
+++ b/src/services/ollama.ts
@@ -296,19 +296,6 @@ export const setWebPrompts = async (prompt: string, followUpPrompt: string) => {
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 () => {
const pageShareUrl = await storage.get("pageShareUrl")
diff --git a/src/services/search.ts b/src/services/search.ts
new file mode 100644
index 0000000..f3851b9
--- /dev/null
+++ b/src/services/search.ts
@@ -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)
+ ])
+}
diff --git a/src/store/option.tsx b/src/store/option.tsx
index 51e41c1..0de6c80 100644
--- a/src/store/option.tsx
+++ b/src/store/option.tsx
@@ -16,6 +16,7 @@ export type Message = {
sources: any[]
images?: string[]
search?: WebSearch
+ id?: string
}
export type ChatHistory = {
diff --git a/src/types/message.ts b/src/types/message.ts
new file mode 100644
index 0000000..66e954e
--- /dev/null
+++ b/src/types/message.ts
@@ -0,0 +1,18 @@
+type WebSearch = {
+ search_engine: string
+ search_url: string
+ search_query: string
+ search_results: {
+ title: string
+ link: string
+ }[]
+ }
+ export type Message = {
+ isBot: boolean
+ name: string
+ message: string
+ sources: any[]
+ images?: string[]
+ search?: WebSearch
+ id?: string
+ }
\ No newline at end of file
diff --git a/src/utils/search-provider.ts b/src/utils/search-provider.ts
new file mode 100644
index 0000000..f23fb8f
--- /dev/null
+++ b/src/utils/search-provider.ts
@@ -0,0 +1,10 @@
+export const SUPPORTED_SERACH_PROVIDERS = [
+ {
+ label: "Google",
+ value: "google"
+ },
+ {
+ label: "DuckDuckGo",
+ value: "duckduckgo"
+ }
+]
\ No newline at end of file
diff --git a/src/web/local-duckduckgo.ts b/src/web/local-duckduckgo.ts
new file mode 100644
index 0000000..51045d7
--- /dev/null
+++ b/src/web/local-duckduckgo.ts
@@ -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
>[] = []
+ 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
+}
diff --git a/src/web/local-google.ts b/src/web/local-google.ts
index 0f6f316..98fa2a8 100644
--- a/src/web/local-google.ts
+++ b/src/web/local-google.ts
@@ -1,3 +1,7 @@
+import {
+ getIsSimpleInternetSearch,
+ totalSearchResults
+} from "@/services/search"
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama"
import type { Document } from "@langchain/core/documents"
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
@@ -5,16 +9,13 @@ import { MemoryVectorStore } from "langchain/vectorstores/memory"
import { cleanUrl } from "~/libs/clean-url"
import { chromeRunTime } from "~/libs/runtime"
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) => {
await chromeRunTime(
@@ -40,23 +41,18 @@ export const localGoogleSearch = async (query: string) => {
(result) => {
const title = result.querySelector("h3")?.textContent
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 }
}
)
- const filteredSearchResults = searchResults
- .filter(
- (result) =>
- !result.link ||
- !BLOCKED_HOSTS.some((host) => result.link.includes(host))
- )
- .filter((result) => result.title && result.link)
- return filteredSearchResults
+ return searchResults
}
-
-export const webSearch = async (query: string) => {
+export const webGoogleSearch = async (query: string) => {
const results = await localGoogleSearch(query)
+ const TOTAL_SEARCH_RESULTS = await totalSearchResults()
const searchResults = results.slice(0, TOTAL_SEARCH_RESULTS)
const isSimpleMode = await getIsSimpleInternetSearch()
@@ -71,7 +67,7 @@ export const webSearch = async (query: string) => {
})
}
- const docs: Document>[] = [];
+ const docs: Document>[] = []
for (const result of searchResults) {
const loader = new PageAssistHtmlLoader({
html: "",
@@ -89,14 +85,14 @@ export const webSearch = async (query: string) => {
const embeddingModle = await defaultEmbeddingModelForRag()
const ollamaEmbedding = new OllamaEmbeddings({
model: embeddingModle || "",
- baseUrl: cleanUrl(ollamaUrl),
+ baseUrl: cleanUrl(ollamaUrl)
})
- const chunkSize = await defaultEmbeddingChunkSize();
- const chunkOverlap = await defaultEmbeddingChunkOverlap();
+ const chunkSize = await defaultEmbeddingChunkSize()
+ const chunkOverlap = await defaultEmbeddingChunkOverlap()
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize,
- chunkOverlap,
+ chunkOverlap
})
const chunks = await textSplitter.splitDocuments(docs)
@@ -105,7 +101,6 @@ export const webSearch = async (query: string) => {
await store.addDocuments(chunks)
-
const resultsWithEmbeddings = await store.similaritySearch(query, 3)
const searchResult = resultsWithEmbeddings.map((result) => {
@@ -116,4 +111,4 @@ export const webSearch = async (query: string) => {
})
return searchResult
-}
\ No newline at end of file
+}
diff --git a/src/web/web.ts b/src/web/web.ts
index e1eccd3..83675e7 100644
--- a/src/web/web.ts
+++ b/src/web/web.ts
@@ -1,42 +1,61 @@
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) => {
- try {
- const hostname = new URL(url).hostname
- return hostname
- } catch (e) {
- return ""
- }
+ try {
+ const hostname = new URL(url).hostname
+ return hostname
+ } catch (e) {
+ return ""
+ }
+}
+
+const searchWeb = (provider: string, query: string) => {
+ switch (provider) {
+ case "duckduckgo":
+ return webDuckDuckGoSearch(query)
+ default:
+ return webGoogleSearch(query)
+ }
}
export const getSystemPromptForWeb = async (query: string) => {
- try {
- const search = await webSearch(query)
+ try {
+ const searchProvider = await getSearchProvider()
+ const search = await searchWeb(searchProvider, query)
- const search_results = search.map((result, idx) => `${result.content}`).join("\n")
+ const search_results = search
+ .map(
+ (result, idx) =>
+ `${result.content}`
+ )
+ .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 {
- prompt,
- source: search.map((result) => {
- return {
- url: result.url,
- name: getHostName(result.url),
- type: "url",
- }
- })
- }
- } catch (e) {
- console.error(e)
- return {
- prompt: "",
- source: [],
+ url: result.url,
+ name: getHostName(result.url),
+ type: "url"
}
+ })
}
-}
\ No newline at end of file
+ } catch (e) {
+ console.error(e)
+ return {
+ prompt: "",
+ source: []
+ }
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 521c779..0ff8b4d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1884,6 +1884,31 @@ charenc@0.0.2:
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
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:
version "3.6.0"
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"
selderee "^0.11.0"
-htmlparser2@^8.0.2:
+htmlparser2@^8.0.1, htmlparser2@^8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
@@ -4998,6 +5023,14 @@ parse5-htmlparser2-tree-adapter@^6.0.0:
dependencies:
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:
version "5.1.1"
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"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
-parse5@^7.1.1:
+parse5@^7.0.0, parse5@^7.1.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
@@ -6974,6 +7007,7 @@ winreg@0.0.12:
integrity sha512-typ/+JRmi7RqP1NanzFULK36vczznSNN8kWVA9vIqXyv8GhghUlwhGp1Xj3Nms1FsPcNnsQrJOR10N58/nQ9hQ==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+ name wrap-ansi-cjs
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==