()
const [dropState, setDropState] = React.useState<
"idle" | "dragging" | "error"
>("idle")
- const {chatMode} = useMessage()
+ const { chatMode, messages, setHistory, setHistoryId, setMessages } =
+ useMessage()
+
+ const setRecentMessagesOnLoad = async () => {
+
+ const isEnabled = await copilotResumeLastChat();
+ if (!isEnabled) {
+ return;
+ }
+ if (messages.length === 0) {
+ const recentChat = await getRecentChatFromCopilot()
+ if (recentChat) {
+ setHistoryId(recentChat.history.id)
+ setHistory(formatToChatHistory(recentChat.messages))
+ setMessages(formatToMessage(recentChat.messages))
+ }
+ }
+ }
+
React.useEffect(() => {
if (!drop.current) {
return
@@ -67,6 +91,12 @@ import { useMessage } from "~/hooks/useMessage"
}
}
}, [])
+
+
+ React.useEffect(() => {
+ setRecentMessagesOnLoad()
+ }, [])
+
return (
{
rewriteUrl
}
}
+
+
+export const copilotResumeLastChat = async () => {
+ return await storage.get("copilotResumeLastChat")
+}
\ No newline at end of file
diff --git a/src/services/model-settings.ts b/src/services/model-settings.ts
new file mode 100644
index 0000000..278576d
--- /dev/null
+++ b/src/services/model-settings.ts
@@ -0,0 +1,101 @@
+import { Storage } from "@plasmohq/storage"
+const storage = new Storage()
+
+type ModelSettings = {
+ f16KV?: boolean
+ frequencyPenalty?: number
+ keepAlive?: string
+ logitsAll?: boolean
+ mirostat?: number
+ mirostatEta?: number
+ mirostatTau?: number
+ numBatch?: number
+ numCtx?: number
+ numGpu?: number
+ numGqa?: number
+ numKeep?: number
+ numPredict?: number
+ numThread?: number
+ penalizeNewline?: boolean
+ presencePenalty?: number
+ repeatLastN?: number
+ repeatPenalty?: number
+ ropeFrequencyBase?: number
+ ropeFrequencyScale?: number
+ temperature?: number
+ tfsZ?: number
+ topK?: number
+ topP?: number
+ typicalP?: number
+ useMLock?: boolean
+ useMMap?: boolean
+ vocabOnly?: boolean
+}
+
+const keys = [
+ "f16KV",
+ "frequencyPenalty",
+ "keepAlive",
+ "logitsAll",
+ "mirostat",
+ "mirostatEta",
+ "mirostatTau",
+ "numBatch",
+ "numCtx",
+ "numGpu",
+ "numGqa",
+ "numKeep",
+ "numPredict",
+ "numThread",
+ "penalizeNewline",
+ "presencePenalty",
+ "repeatLastN",
+ "repeatPenalty",
+ "ropeFrequencyBase",
+ "ropeFrequencyScale",
+ "temperature",
+ "tfsZ",
+ "topK",
+ "topP",
+ "typicalP",
+ "useMLock",
+ "useMMap",
+ "vocabOnly"
+]
+
+const getAllModelSettings = async () => {
+ try {
+ const settings: ModelSettings = {}
+ for (const key of keys) {
+ const value = await storage.get(key)
+ settings[key] = value
+ if (!value && key === "keepAlive") {
+ settings[key] = "5m"
+ }
+
+ }
+ return settings
+ } catch (error) {
+ console.error(error)
+ return {}
+ }
+}
+
+const setModelSetting = async (key: string,
+ value: string | number | boolean) => {
+ await storage.set(key, value)
+}
+
+export const getAllDefaultModelSettings = async (): Promise => {
+ const settings: ModelSettings = {}
+ for (const key of keys) {
+ const value = await storage.get(key)
+ settings[key] = value
+ if (!value && key === "keepAlive") {
+ settings[key] = "5m"
+ }
+ }
+ return settings
+}
+
+export { getAllModelSettings, setModelSetting }
\ No newline at end of file
diff --git a/src/store/model.tsx b/src/store/model.tsx
new file mode 100644
index 0000000..8fadca8
--- /dev/null
+++ b/src/store/model.tsx
@@ -0,0 +1,136 @@
+import { create } from "zustand"
+
+type CurrentChatModelSettings = {
+ f16KV?: boolean
+ frequencyPenalty?: number
+ keepAlive?: string
+ logitsAll?: boolean
+ mirostat?: number
+ mirostatEta?: number
+ mirostatTau?: number
+ numBatch?: number
+ numCtx?: number
+ numGpu?: number
+ numGqa?: number
+ numKeep?: number
+ numPredict?: number
+ numThread?: number
+ penalizeNewline?: boolean
+ presencePenalty?: number
+ repeatLastN?: number
+ repeatPenalty?: number
+ ropeFrequencyBase?: number
+ ropeFrequencyScale?: number
+ temperature?: number
+ tfsZ?: number
+ topK?: number
+ topP?: number
+ typicalP?: number
+ useMLock?: boolean
+ useMMap?: boolean
+ vocabOnly?: boolean
+ seed?: number
+
+ setF16KV?: (f16KV: boolean) => void
+ setFrequencyPenalty?: (frequencyPenalty: number) => void
+ setKeepAlive?: (keepAlive: string) => void
+ setLogitsAll?: (logitsAll: boolean) => void
+ setMirostat?: (mirostat: number) => void
+ setMirostatEta?: (mirostatEta: number) => void
+ setMirostatTau?: (mirostatTau: number) => void
+ setNumBatch?: (numBatch: number) => void
+ setNumCtx?: (numCtx: number) => void
+ setNumGpu?: (numGpu: number) => void
+ setNumGqa?: (numGqa: number) => void
+ setNumKeep?: (numKeep: number) => void
+ setNumPredict?: (numPredict: number) => void
+ setNumThread?: (numThread: number) => void
+ setPenalizeNewline?: (penalizeNewline: boolean) => void
+ setPresencePenalty?: (presencePenalty: number) => void
+ setRepeatLastN?: (repeatLastN: number) => void
+ setRepeatPenalty?: (repeatPenalty: number) => void
+ setRopeFrequencyBase?: (ropeFrequencyBase: number) => void
+ setRopeFrequencyScale?: (ropeFrequencyScale: number) => void
+ setTemperature?: (temperature: number) => void
+ setTfsZ?: (tfsZ: number) => void
+ setTopK?: (topK: number) => void
+ setTopP?: (topP: number) => void
+ setTypicalP?: (typicalP: number) => void
+ setUseMLock?: (useMLock: boolean) => void
+ setUseMMap?: (useMMap: boolean) => void
+ setVocabOnly?: (vocabOnly: boolean) => void
+ seetSeed?: (seed: number) => void
+
+ setX: (key: string, value: any) => void
+ reset: () => void
+}
+
+export const useStoreChatModelSettings = create(
+ (set) => ({
+ setF16KV: (f16KV: boolean) => set({ f16KV }),
+ setFrequencyPenalty: (frequencyPenalty: number) =>
+ set({ frequencyPenalty }),
+ setKeepAlive: (keepAlive: string) => set({ keepAlive }),
+ setLogitsAll: (logitsAll: boolean) => set({ logitsAll }),
+ setMirostat: (mirostat: number) => set({ mirostat }),
+ setMirostatEta: (mirostatEta: number) => set({ mirostatEta }),
+ setMirostatTau: (mirostatTau: number) => set({ mirostatTau }),
+ setNumBatch: (numBatch: number) => set({ numBatch }),
+ setNumCtx: (numCtx: number) => set({ numCtx }),
+ setNumGpu: (numGpu: number) => set({ numGpu }),
+ setNumGqa: (numGqa: number) => set({ numGqa }),
+ setNumKeep: (numKeep: number) => set({ numKeep }),
+ setNumPredict: (numPredict: number) => set({ numPredict }),
+ setNumThread: (numThread: number) => set({ numThread }),
+ setPenalizeNewline: (penalizeNewline: boolean) => set({ penalizeNewline }),
+ setPresencePenalty: (presencePenalty: number) => set({ presencePenalty }),
+ setRepeatLastN: (repeatLastN: number) => set({ repeatLastN }),
+ setRepeatPenalty: (repeatPenalty: number) => set({ repeatPenalty }),
+ setRopeFrequencyBase: (ropeFrequencyBase: number) =>
+ set({ ropeFrequencyBase }),
+ setRopeFrequencyScale: (ropeFrequencyScale: number) =>
+ set({ ropeFrequencyScale }),
+ setTemperature: (temperature: number) => set({ temperature }),
+ setTfsZ: (tfsZ: number) => set({ tfsZ }),
+ setTopK: (topK: number) => set({ topK }),
+ setTopP: (topP: number) => set({ topP }),
+ setTypicalP: (typicalP: number) => set({ typicalP }),
+ setUseMLock: (useMLock: boolean) => set({ useMLock }),
+ setUseMMap: (useMMap: boolean) => set({ useMMap }),
+ setVocabOnly: (vocabOnly: boolean) => set({ vocabOnly }),
+ seetSeed: (seed: number) => set({ seed }),
+ setX: (key: string, value: any) => set({ [key]: value }),
+ reset: () =>
+ set({
+ f16KV: undefined,
+ frequencyPenalty: undefined,
+ keepAlive: undefined,
+ logitsAll: undefined,
+ mirostat: undefined,
+ mirostatEta: undefined,
+ mirostatTau: undefined,
+ numBatch: undefined,
+ numCtx: undefined,
+ numGpu: undefined,
+ numGqa: undefined,
+ numKeep: undefined,
+ numPredict: undefined,
+ numThread: undefined,
+ penalizeNewline: undefined,
+ presencePenalty: undefined,
+ repeatLastN: undefined,
+ repeatPenalty: undefined,
+ ropeFrequencyBase: undefined,
+ ropeFrequencyScale: undefined,
+ temperature: undefined,
+ tfsZ: undefined,
+ topK: undefined,
+ topP: undefined,
+ typicalP: undefined,
+ useMLock: undefined,
+ useMMap: undefined,
+ vocabOnly: undefined,
+ seed: undefined
+ })
+ })
+)
diff --git a/src/utils/search-provider.ts b/src/utils/search-provider.ts
index 5fb35e6..d623df2 100644
--- a/src/utils/search-provider.ts
+++ b/src/utils/search-provider.ts
@@ -10,5 +10,9 @@ export const SUPPORTED_SERACH_PROVIDERS = [
{
label: "Sogou",
value: "sogou"
+ },
+ {
+ label: "Brave",
+ value: "brave"
}
]
\ No newline at end of file
diff --git a/src/utils/to-source.ts b/src/utils/to-source.ts
index ecc5454..2f148f5 100644
--- a/src/utils/to-source.ts
+++ b/src/utils/to-source.ts
@@ -10,6 +10,7 @@ export const toBase64 = (file: File | Blob): Promise => {
})
}
+
export const toArrayBufferFromBase64 = async (base64: string) => {
const res = await fetch(base64)
const blob = await res.blob()
diff --git a/src/web/search-engines/brave.ts b/src/web/search-engines/brave.ts
new file mode 100644
index 0000000..b387fbf
--- /dev/null
+++ b/src/web/search-engines/brave.ts
@@ -0,0 +1,112 @@
+import { cleanUrl } from "@/libs/clean-url"
+import { urlRewriteRuntime } 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 localBraveSearch = async (query: string) => {
+ await urlRewriteRuntime(cleanUrl("https://search.brave.com/search?q=" + query), "duckduckgo")
+
+ const abortController = new AbortController()
+ setTimeout(() => abortController.abort(), 10000)
+
+ const htmlString = await fetch(
+ "https://search.brave.com/search?q=" + query,
+ {
+ signal: abortController.signal
+ }
+ )
+ .then((response) => response.text())
+ .catch()
+
+ const $ = cheerio.load(htmlString)
+ const $results = $("div#results")
+ const $snippets = $results.find("div.snippet")
+
+ const searchResults = Array.from($snippets).map((result) => {
+ const link = $(result).find("a").attr("href")
+ const title = $(result).find("div.title").text()
+ const content = $(result).find("div.snippet-description").text()
+ return { title, link, content }
+ }).filter((result) => result.link && result.title && result.content)
+
+ console.log(searchResults)
+
+ return searchResults
+}
+
+export const webBraveSearch = async (query: string) => {
+ const results = await localBraveSearch(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/web.ts b/src/web/web.ts
index cfadf09..e9c1765 100644
--- a/src/web/web.ts
+++ b/src/web/web.ts
@@ -3,6 +3,7 @@ import { webGoogleSearch } from "./search-engines/google"
import { webDuckDuckGoSearch } from "./search-engines/duckduckgo"
import { getSearchProvider } from "@/services/search"
import { webSogouSearch } from "./search-engines/sogou"
+import { webBraveSearch } from "./search-engines/brave"
const getHostName = (url: string) => {
try {
@@ -19,6 +20,8 @@ const searchWeb = (provider: string, query: string) => {
return webDuckDuckGoSearch(query)
case "sogou":
return webSogouSearch(query)
+ case "brave":
+ return webBraveSearch(query)
default:
return webGoogleSearch(query)
}
diff --git a/wxt.config.ts b/wxt.config.ts
index aa890c4..69fab0a 100644
--- a/wxt.config.ts
+++ b/wxt.config.ts
@@ -48,7 +48,7 @@ export default defineConfig({
outDir: "build",
manifest: {
- version: "1.1.8",
+ version: "1.1.9",
name:
process.env.TARGET === "firefox"
? "Page Assist - A Web UI for Local AI Models"