1
.gitignore
vendored
@ -40,3 +40,4 @@ keys.json
|
|||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
.tsbuildinfo
|
.tsbuildinfo
|
||||||
|
.wxt
|
43
package.json
@ -5,9 +5,14 @@
|
|||||||
"description": "Use your locally running AI models to assist you in your web browsing.",
|
"description": "Use your locally running AI models to assist you in your web browsing.",
|
||||||
"author": "n4ze3m",
|
"author": "n4ze3m",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "plasmo dev",
|
"dev": "wxt",
|
||||||
"build": "plasmo build",
|
"dev:firefox": "wxt -b firefox",
|
||||||
"package": "plasmo package"
|
"build": "wxt build",
|
||||||
|
"build:firefox": "wxt build -b firefox",
|
||||||
|
"zip": "wxt zip",
|
||||||
|
"zip:firefox": "wxt zip -b firefox",
|
||||||
|
"compile": "tsc --noEmit",
|
||||||
|
"postinstall": "wxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/cssinjs": "^1.18.4",
|
"@ant-design/cssinjs": "^1.18.4",
|
||||||
@ -21,6 +26,7 @@
|
|||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@tanstack/react-query": "^5.17.19",
|
"@tanstack/react-query": "^5.17.19",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"antd": "^5.13.3",
|
"antd": "^5.13.3",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
@ -53,34 +59,7 @@
|
|||||||
"postcss": "^8.4.33",
|
"postcss": "^8.4.33",
|
||||||
"prettier": "3.2.4",
|
"prettier": "3.2.4",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "5.3.3"
|
"typescript": "5.3.3",
|
||||||
},
|
"wxt": "^0.17.7"
|
||||||
"manifest": {
|
|
||||||
"host_permissions": [
|
|
||||||
"http://*/*",
|
|
||||||
"https://*/*"
|
|
||||||
],
|
|
||||||
"commands": {
|
|
||||||
"_execute_action": {
|
|
||||||
"suggested_key": {
|
|
||||||
"default": "Ctrl+Shift+L"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"execute_side_panel": {
|
|
||||||
"description": "Open the side panel",
|
|
||||||
"suggested_key": {
|
|
||||||
"default": "Ctrl+Shift+P"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"permissions": [
|
|
||||||
"storage",
|
|
||||||
"activeTab",
|
|
||||||
"scripting",
|
|
||||||
"declarativeNetRequest",
|
|
||||||
"action",
|
|
||||||
"unlimitedStorage",
|
|
||||||
"contextMenus"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@ -1,138 +0,0 @@
|
|||||||
import { getOllamaURL, isOllamaRunning } from "~services/ollama"
|
|
||||||
|
|
||||||
export {}
|
|
||||||
|
|
||||||
const progressHuman = (completed: number, total: number) => {
|
|
||||||
return ((completed / total) * 100).toFixed(0) + "%"
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearBadge = () => {
|
|
||||||
chrome.action.setBadgeText({ text: "" })
|
|
||||||
chrome.action.setTitle({ title: "" })
|
|
||||||
}
|
|
||||||
|
|
||||||
const streamDownload = async (url: string, model: string) => {
|
|
||||||
url += "/api/pull"
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ model, stream: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
const reader = response.body?.getReader()
|
|
||||||
|
|
||||||
const decoder = new TextDecoder()
|
|
||||||
|
|
||||||
let isSuccess = true
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read()
|
|
||||||
|
|
||||||
if (done) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = decoder.decode(value)
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(text.trim()) as {
|
|
||||||
status: string
|
|
||||||
total?: number
|
|
||||||
completed?: number
|
|
||||||
}
|
|
||||||
if (json.total && json.completed) {
|
|
||||||
chrome.action.setBadgeText({
|
|
||||||
text: progressHuman(json.completed, json.total)
|
|
||||||
})
|
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#0000FF" })
|
|
||||||
} else {
|
|
||||||
chrome.action.setBadgeText({ text: "🏋️♂️" })
|
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#FFFFFF" })
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.action.setTitle({ title: json.status })
|
|
||||||
|
|
||||||
if (json.status === "success") {
|
|
||||||
isSuccess = true
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSuccess) {
|
|
||||||
chrome.action.setBadgeText({ text: "✅" })
|
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#00FF00" })
|
|
||||||
chrome.action.setTitle({ title: "Model pulled successfully" })
|
|
||||||
} else {
|
|
||||||
chrome.action.setBadgeText({ text: "❌" })
|
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#FF0000" })
|
|
||||||
chrome.action.setTitle({ title: "Model pull failed" })
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
clearBadge()
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(async (message) => {
|
|
||||||
if (message.type === "sidepanel") {
|
|
||||||
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
|
||||||
const tab = tabs[0]
|
|
||||||
await chrome.sidePanel.open({
|
|
||||||
tabId: tab.id
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else if (message.type === "pull_model") {
|
|
||||||
const ollamaURL = await getOllamaURL()
|
|
||||||
|
|
||||||
const isRunning = await isOllamaRunning()
|
|
||||||
|
|
||||||
if (!isRunning) {
|
|
||||||
chrome.action.setBadgeText({ text: "E" })
|
|
||||||
chrome.action.setBadgeBackgroundColor({ color: "#FF0000" })
|
|
||||||
chrome.action.setTitle({ title: "Ollama is not running" })
|
|
||||||
setTimeout(() => {
|
|
||||||
clearBadge()
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
await streamDownload(ollamaURL, message.modelName)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
chrome.action.onClicked.addListener((tab) => {
|
|
||||||
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") })
|
|
||||||
})
|
|
||||||
|
|
||||||
chrome.commands.onCommand.addListener((command) => {
|
|
||||||
switch (command) {
|
|
||||||
case "execute_side_panel":
|
|
||||||
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
|
||||||
const tab = tabs[0]
|
|
||||||
await chrome.sidePanel.open({
|
|
||||||
tabId: tab.id
|
|
||||||
})
|
|
||||||
})
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
chrome.contextMenus.create({
|
|
||||||
id: "open-side-panel-pa",
|
|
||||||
title: "Open Side Panel to Chat",
|
|
||||||
contexts: ["all"]
|
|
||||||
})
|
|
||||||
|
|
||||||
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
|
||||||
if (info.menuItemId === "open-side-panel-pa") {
|
|
||||||
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
|
||||||
const tab = tabs[0]
|
|
||||||
await chrome.sidePanel.open({
|
|
||||||
tabId: tab.id
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
@ -14,7 +14,7 @@ import {
|
|||||||
RunnableMap,
|
RunnableMap,
|
||||||
RunnableSequence,
|
RunnableSequence,
|
||||||
} from "langchain/schema/runnable";
|
} from "langchain/schema/runnable";
|
||||||
import type { ChatHistory } from "~store";
|
import type { ChatHistory } from "~/store";
|
||||||
type RetrievalChainInput = {
|
type RetrievalChainInput = {
|
||||||
chat_history: string;
|
chat_history: string;
|
||||||
question: string;
|
question: string;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import useDynamicTextareaSize from "~hooks/useDynamicTextareaSize"
|
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string
|
value: string
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Form, Image, Input, Modal, Tooltip, message } from "antd"
|
import { Form, Image, Input, Modal, Tooltip, message } from "antd"
|
||||||
import { Share } from "lucide-react"
|
import { Share } from "lucide-react"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import type { Message } from "~store/option"
|
import type { Message } from "~/store/option"
|
||||||
import Markdown from "./Markdown"
|
import Markdown from "./Markdown"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { useMutation } from "@tanstack/react-query"
|
import { useMutation } from "@tanstack/react-query"
|
||||||
import { getPageShareUrl } from "~services/ollama"
|
import { getPageShareUrl } from "~/services/ollama"
|
||||||
import { cleanUrl } from "~libs/clean-url"
|
import { cleanUrl } from "~/libs/clean-url"
|
||||||
import { getUserId, saveWebshare } from "~libs/db"
|
import { getUserId, saveWebshare } from "~/libs/db"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
messages: Message[]
|
messages: Message[]
|
||||||
|
@ -4,8 +4,8 @@ import { useLocation, NavLink } from "react-router-dom"
|
|||||||
import { Sidebar } from "../Option/Sidebar"
|
import { Sidebar } from "../Option/Sidebar"
|
||||||
import { Drawer, Select, Tooltip } from "antd"
|
import { Drawer, Select, Tooltip } from "antd"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { getAllModels } from "~services/ollama"
|
import { getAllModels } from "~/services/ollama"
|
||||||
import { useMessageOption } from "~hooks/useMessageOption"
|
import { useMessageOption } from "~/hooks/useMessageOption"
|
||||||
import {
|
import {
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
CogIcon,
|
CogIcon,
|
||||||
@ -15,8 +15,8 @@ import {
|
|||||||
SquarePen,
|
SquarePen,
|
||||||
ZapIcon
|
ZapIcon
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { getAllPrompts } from "~libs/db"
|
import { getAllPrompts } from "~/libs/db"
|
||||||
import { ShareBtn } from "~components/Common/ShareBtn"
|
import { ShareBtn } from "~/components/Common/ShareBtn"
|
||||||
|
|
||||||
export default function OptionLayout({
|
export default function OptionLayout({
|
||||||
children
|
children
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { Skeleton, Table, Tag, Tooltip, notification, Modal, Input } from "antd"
|
import { Skeleton, Table, Tag, Tooltip, notification, Modal, Input } from "antd"
|
||||||
import { bytePerSecondFormatter } from "~libs/byte-formater"
|
import { bytePerSecondFormatter } from "~/libs/byte-formater"
|
||||||
import { deleteModel, getAllModels } from "~services/ollama"
|
import { deleteModel, getAllModels } from "~/services/ollama"
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import relativeTime from "dayjs/plugin/relativeTime"
|
import relativeTime from "dayjs/plugin/relativeTime"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { useMessageOption } from "~hooks/useMessageOption"
|
import { useMessageOption } from "~/hooks/useMessageOption"
|
||||||
import { PlaygroundEmpty } from "./PlaygroundEmpty"
|
import { PlaygroundEmpty } from "./PlaygroundEmpty"
|
||||||
import { PlaygroundMessage } from "~components/Common/Playground/Message"
|
import { PlaygroundMessage } from "~/components/Common/Playground/Message"
|
||||||
|
|
||||||
export const PlaygroundChat = () => {
|
export const PlaygroundChat = () => {
|
||||||
const {
|
const {
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
getOllamaURL,
|
getOllamaURL,
|
||||||
isOllamaRunning,
|
isOllamaRunning,
|
||||||
setOllamaURL as saveOllamaURL
|
setOllamaURL as saveOllamaURL
|
||||||
} from "~services/ollama"
|
} from "~/services/ollama"
|
||||||
|
|
||||||
export const PlaygroundEmpty = () => {
|
export const PlaygroundEmpty = () => {
|
||||||
const [ollamaURL, setOllamaURL] = useState<string>("")
|
const [ollamaURL, setOllamaURL] = useState<string>("")
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import useDynamicTextareaSize from "~hooks/useDynamicTextareaSize"
|
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
||||||
import { toBase64 } from "~libs/to-base64"
|
import { toBase64 } from "~/libs/to-base64"
|
||||||
import { useMessageOption } from "~hooks/useMessageOption"
|
import { useMessageOption } from "~/hooks/useMessageOption"
|
||||||
import { Checkbox, Dropdown, Switch, Tooltip } from "antd"
|
import { Checkbox, Dropdown, Switch, Tooltip } from "antd"
|
||||||
import { Image } from "antd"
|
import { Image } from "antd"
|
||||||
import { useSpeechRecognition } from "~hooks/useSpeechRecognition"
|
import { useSpeechRecognition } from "~/hooks/useSpeechRecognition"
|
||||||
import { useWebUI } from "~store/webui"
|
import { useWebUI } from "~/store/webui"
|
||||||
import { defaultEmbeddingModelForRag } from "~services/ollama"
|
import { defaultEmbeddingModelForRag } from "~/services/ollama"
|
||||||
import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react"
|
import { ImageIcon, MicIcon, StopCircleIcon, X } from "lucide-react"
|
||||||
import { getVariable } from "~utils/select-varaible"
|
import { getVariable } from "~/utils/select-varaible"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
dropedFile: File | undefined
|
dropedFile: File | undefined
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
getAllPrompts,
|
getAllPrompts,
|
||||||
savePrompt,
|
savePrompt,
|
||||||
updatePrompt
|
updatePrompt
|
||||||
} from "~libs/db"
|
} from "~/libs/db"
|
||||||
|
|
||||||
export const PromptBody = () => {
|
export const PromptBody = () => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useMutation, useQuery } from "@tanstack/react-query"
|
import { useMutation, useQuery } from "@tanstack/react-query"
|
||||||
import { Form, InputNumber, Select, Skeleton } from "antd"
|
import { Form, InputNumber, Select, Skeleton } from "antd"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { SaveButton } from "~components/Common/SaveButton"
|
import { SaveButton } from "~/components/Common/SaveButton"
|
||||||
import {
|
import {
|
||||||
defaultEmbeddingChunkOverlap,
|
defaultEmbeddingChunkOverlap,
|
||||||
defaultEmbeddingChunkSize,
|
defaultEmbeddingChunkSize,
|
||||||
@ -10,7 +10,7 @@ import {
|
|||||||
getOllamaURL,
|
getOllamaURL,
|
||||||
saveForRag,
|
saveForRag,
|
||||||
setOllamaURL as saveOllamaURL
|
setOllamaURL as saveOllamaURL
|
||||||
} from "~services/ollama"
|
} from "~/services/ollama"
|
||||||
import { SettingPrompt } from "./prompt"
|
import { SettingPrompt } from "./prompt"
|
||||||
|
|
||||||
export const SettingsOllama = () => {
|
export const SettingsOllama = () => {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { useQueryClient } from "@tanstack/react-query"
|
import { useQueryClient } from "@tanstack/react-query"
|
||||||
import { useDarkMode } from "~hooks/useDarkmode"
|
import { useDarkMode } from "~/hooks/useDarkmode"
|
||||||
import { useMessageOption } from "~hooks/useMessageOption"
|
import { useMessageOption } from "~/hooks/useMessageOption"
|
||||||
import { PageAssitDatabase } from "~libs/db"
|
import { PageAssitDatabase } from "~/libs/db"
|
||||||
import { Select } from "antd"
|
import { Select } from "antd"
|
||||||
import { SUPPORTED_LANGUAGES } from "~utils/supporetd-languages"
|
import { SUPPORTED_LANGUAGES } from "~/utils/supporetd-languages"
|
||||||
import { MoonIcon, SunIcon } from "lucide-react"
|
import { MoonIcon, SunIcon } from "lucide-react"
|
||||||
import { SearchModeSettings } from "./search-mode"
|
import { SearchModeSettings } from "./search-mode"
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { Skeleton, Radio, Form, Alert } from "antd"
|
import { Skeleton, Radio, Form, Alert } from "antd"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { SaveButton } from "~components/Common/SaveButton"
|
import { SaveButton } from "~/components/Common/SaveButton"
|
||||||
import {
|
import {
|
||||||
getWebSearchPrompt,
|
getWebSearchPrompt,
|
||||||
setSystemPromptForNonRagOption,
|
setSystemPromptForNonRagOption,
|
||||||
systemPromptForNonRagOption,
|
systemPromptForNonRagOption,
|
||||||
geWebSearchFollowUpPrompt,
|
geWebSearchFollowUpPrompt,
|
||||||
setWebPrompts
|
setWebPrompts
|
||||||
} from "~services/ollama"
|
} from "~/services/ollama"
|
||||||
|
|
||||||
export const SettingPrompt = () => {
|
export const SettingPrompt = () => {
|
||||||
const [selectedValue, setSelectedValue] = React.useState<"normal" | "web">(
|
const [selectedValue, setSelectedValue] = React.useState<"normal" | "web">(
|
||||||
|
@ -3,7 +3,7 @@ import { Skeleton, Switch } from "antd"
|
|||||||
import {
|
import {
|
||||||
getIsSimpleInternetSearch,
|
getIsSimpleInternetSearch,
|
||||||
setIsSimpleInternetSearch
|
setIsSimpleInternetSearch
|
||||||
} from "~services/ollama"
|
} from "~/services/ollama"
|
||||||
|
|
||||||
export const SearchModeSettings = () => {
|
export const SearchModeSettings = () => {
|
||||||
const { data, status } = useQuery({
|
const { data, status } = useQuery({
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||||
import { Form, Input, Skeleton, Table, Tooltip, message } from "antd"
|
import { Form, Input, Skeleton, Table, Tooltip, message } from "antd"
|
||||||
import { Trash2 } from "lucide-react"
|
import { Trash2 } from "lucide-react"
|
||||||
import { SaveButton } from "~components/Common/SaveButton"
|
import { SaveButton } from "~/components/Common/SaveButton"
|
||||||
import { deleteWebshare, getAllWebshares, getUserId } from "~libs/db"
|
import { deleteWebshare, getAllWebshares, getUserId } from "~/libs/db"
|
||||||
import { getPageShareUrl, setPageShareUrl } from "~services/ollama"
|
import { getPageShareUrl, setPageShareUrl } from "~/services/ollama"
|
||||||
import { verifyPageShareURL } from "~utils/verify-page-share"
|
import { verifyPageShareURL } from "~/utils/verify-page-share"
|
||||||
|
|
||||||
export const OptionShareBody = () => {
|
export const OptionShareBody = () => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
@ -5,9 +5,9 @@ import {
|
|||||||
formatToMessage,
|
formatToMessage,
|
||||||
deleteByHistoryId,
|
deleteByHistoryId,
|
||||||
updateHistory
|
updateHistory
|
||||||
} from "~libs/db"
|
} from "~/libs/db"
|
||||||
import { Empty, Skeleton } from "antd"
|
import { Empty, Skeleton } from "antd"
|
||||||
import { useMessageOption } from "~hooks/useMessageOption"
|
import { useMessageOption } from "~/hooks/useMessageOption"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { PencilIcon, Trash2 } from "lucide-react"
|
import { PencilIcon, Trash2 } from "lucide-react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { PlaygroundMessage } from "~components/Common/Playground/Message"
|
import { PlaygroundMessage } from "~/components/Common/Playground/Message"
|
||||||
import { useMessage } from "~hooks/useMessage"
|
import { useMessage } from "~/hooks/useMessage"
|
||||||
import { EmptySidePanel } from "../Chat/empty"
|
import { EmptySidePanel } from "../Chat/empty"
|
||||||
|
|
||||||
export const SidePanelBody = () => {
|
export const SidePanelBody = () => {
|
||||||
|
@ -2,13 +2,13 @@ import { useQuery } from "@tanstack/react-query"
|
|||||||
import { Select } from "antd"
|
import { Select } from "antd"
|
||||||
import { RotateCcw } from "lucide-react"
|
import { RotateCcw } from "lucide-react"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useMessage } from "~hooks/useMessage"
|
import { useMessage } from "~/hooks/useMessage"
|
||||||
import {
|
import {
|
||||||
getAllModels,
|
getAllModels,
|
||||||
getOllamaURL,
|
getOllamaURL,
|
||||||
isOllamaRunning,
|
isOllamaRunning,
|
||||||
setOllamaURL as saveOllamaURL
|
setOllamaURL as saveOllamaURL
|
||||||
} from "~services/ollama"
|
} from "~/services/ollama"
|
||||||
|
|
||||||
export const EmptySidePanel = () => {
|
export const EmptySidePanel = () => {
|
||||||
const [ollamaURL, setOllamaURL] = useState<string>("")
|
const [ollamaURL, setOllamaURL] = useState<string>("")
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useForm } from "@mantine/form"
|
import { useForm } from "@mantine/form"
|
||||||
import { useMutation } from "@tanstack/react-query"
|
import { useMutation } from "@tanstack/react-query"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import useDynamicTextareaSize from "~hooks/useDynamicTextareaSize"
|
import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
|
||||||
import { useMessage } from "~hooks/useMessage"
|
import { useMessage } from "~/hooks/useMessage"
|
||||||
import { toBase64 } from "~libs/to-base64"
|
import { toBase64 } from "~/libs/to-base64"
|
||||||
import { Checkbox, Dropdown, Image, Tooltip } from "antd"
|
import { Checkbox, Dropdown, Image, Tooltip } from "antd"
|
||||||
import { useSpeechRecognition } from "~hooks/useSpeechRecognition"
|
import { useSpeechRecognition } from "~/hooks/useSpeechRecognition"
|
||||||
import { useWebUI } from "~store/webui"
|
import { useWebUI } from "~/store/webui"
|
||||||
import { defaultEmbeddingModelForRag } from "~services/ollama"
|
import { defaultEmbeddingModelForRag } from "~/services/ollama"
|
||||||
import { ImageIcon, MicIcon, X } from "lucide-react"
|
import { ImageIcon, MicIcon, X } from "lucide-react"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import logoImage from "data-base64:~assets/icon.png"
|
import logoImage from "~/assets/icon.png"
|
||||||
import { useMessage } from "~hooks/useMessage"
|
import { useMessage } from "~/hooks/useMessage"
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
import { Tooltip } from "antd"
|
import { Tooltip } from "antd"
|
||||||
import { BoxesIcon, CogIcon, RefreshCcw } from "lucide-react"
|
import { BoxesIcon, CogIcon, RefreshCcw } from "lucide-react"
|
||||||
|
@ -12,13 +12,13 @@ import {
|
|||||||
defaultEmbeddingChunkSize,
|
defaultEmbeddingChunkSize,
|
||||||
defaultEmbeddingModelForRag,
|
defaultEmbeddingModelForRag,
|
||||||
saveForRag
|
saveForRag
|
||||||
} from "~services/ollama"
|
} from "~/services/ollama"
|
||||||
|
|
||||||
import { Skeleton, Radio, Select, Form, InputNumber } from "antd"
|
import { Skeleton, Radio, Select, Form, InputNumber } from "antd"
|
||||||
import { useDarkMode } from "~hooks/useDarkmode"
|
import { useDarkMode } from "~/hooks/useDarkmode"
|
||||||
import { SaveButton } from "~components/Common/SaveButton"
|
import { SaveButton } from "~/components/Common/SaveButton"
|
||||||
import { SUPPORTED_LANGUAGES } from "~utils/supporetd-languages"
|
import { SUPPORTED_LANGUAGES } from "~/utils/supporetd-languages"
|
||||||
import { useMessage } from "~hooks/useMessage"
|
import { useMessage } from "~/hooks/useMessage"
|
||||||
import { MoonIcon, SunIcon } from "lucide-react"
|
import { MoonIcon, SunIcon } from "lucide-react"
|
||||||
|
|
||||||
export const SettingsBody = () => {
|
export const SettingsBody = () => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import logoImage from "data-base64:~assets/icon.png"
|
|
||||||
import { ChevronLeft } from "lucide-react"
|
import { ChevronLeft } from "lucide-react"
|
||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
|
import logoImage from "~/assets/icon.png"
|
||||||
|
|
||||||
export const SidepanelSettingsHeader = () => {
|
export const SidepanelSettingsHeader = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex px-3 justify-start gap-3 bg-white dark:bg-[#171717] border-b border-gray-300 dark:border-gray-700 py-4 items-center">
|
<div className="flex px-3 justify-start gap-3 bg-white dark:bg-[#171717] border-b border-gray-300 dark:border-gray-700 py-4 items-center">
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
import type { PlasmoCSConfig } from "plasmo"
|
|
||||||
|
|
||||||
export const config: PlasmoCSConfig = {
|
|
||||||
matches: ["*://ollama.com/library/*"],
|
|
||||||
all_frames: true
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadModel = async (modelName: string) => {
|
|
||||||
const ok = confirm(
|
|
||||||
`[Page Assist Extension] Do you want to pull ${modelName} model? This has nothing to do with Ollama.com website. The model will be pulled locally once you confirm.`
|
|
||||||
)
|
|
||||||
if (ok) {
|
|
||||||
alert(
|
|
||||||
`[Page Assist Extension] Pulling ${modelName} model. For more details, check the extension icon.`
|
|
||||||
)
|
|
||||||
|
|
||||||
await chrome.runtime.sendMessage({
|
|
||||||
type: "pull_model",
|
|
||||||
modelName
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadSVG = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5 pageasssist-icon">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
|
||||||
</svg>
|
|
||||||
`
|
|
||||||
const codeDiv = document.querySelectorAll("div.language-none")
|
|
||||||
|
|
||||||
for (let i = 0; i < codeDiv.length; i++) {
|
|
||||||
const button = codeDiv[i].querySelector("button")
|
|
||||||
const command = codeDiv[i].querySelector("input")
|
|
||||||
if (button && command) {
|
|
||||||
const newButton = document.createElement("button")
|
|
||||||
newButton.innerHTML = downloadSVG
|
|
||||||
newButton.className = `border-l ${button.className}`
|
|
||||||
newButton.id = `download-${i}-pageassist`
|
|
||||||
const modelName = command?.value
|
|
||||||
.replace("ollama run", "")
|
|
||||||
.replace("ollama pull", "")
|
|
||||||
.trim()
|
|
||||||
newButton.addEventListener("click", () => {
|
|
||||||
downloadModel(modelName)
|
|
||||||
})
|
|
||||||
|
|
||||||
const span = document.createElement("span")
|
|
||||||
span.title = "Download model via Page Assist"
|
|
||||||
span.appendChild(newButton)
|
|
||||||
|
|
||||||
button.parentNode.appendChild(span)
|
|
||||||
}
|
|
||||||
}
|
|
142
src/entries/background.ts
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
|
||||||
|
import { getOllamaURL, isOllamaRunning } from "../services/ollama"
|
||||||
|
const progressHuman = (completed: number, total: number) => {
|
||||||
|
return ((completed / total) * 100).toFixed(0) + "%"
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearBadge = () => {
|
||||||
|
chrome.action.setBadgeText({ text: "" })
|
||||||
|
chrome.action.setTitle({ title: "" })
|
||||||
|
}
|
||||||
|
const streamDownload = async (url: string, model: string) => {
|
||||||
|
url += "/api/pull"
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ model, stream: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
const reader = response.body?.getReader()
|
||||||
|
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
|
||||||
|
let isSuccess = true
|
||||||
|
while (true) {
|
||||||
|
if (!reader) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const { done, value } = await reader.read()
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = decoder.decode(value)
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(text.trim()) as {
|
||||||
|
status: string
|
||||||
|
total?: number
|
||||||
|
completed?: number
|
||||||
|
}
|
||||||
|
if (json.total && json.completed) {
|
||||||
|
chrome.action.setBadgeText({
|
||||||
|
text: progressHuman(json.completed, json.total)
|
||||||
|
})
|
||||||
|
chrome.action.setBadgeBackgroundColor({ color: "#0000FF" })
|
||||||
|
} else {
|
||||||
|
chrome.action.setBadgeText({ text: "🏋️♂️" })
|
||||||
|
chrome.action.setBadgeBackgroundColor({ color: "#FFFFFF" })
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.action.setTitle({ title: json.status })
|
||||||
|
|
||||||
|
if (json.status === "success") {
|
||||||
|
isSuccess = true
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
chrome.action.setBadgeText({ text: "✅" })
|
||||||
|
chrome.action.setBadgeBackgroundColor({ color: "#00FF00" })
|
||||||
|
chrome.action.setTitle({ title: "Model pulled successfully" })
|
||||||
|
} else {
|
||||||
|
chrome.action.setBadgeText({ text: "❌" })
|
||||||
|
chrome.action.setBadgeBackgroundColor({ color: "#FF0000" })
|
||||||
|
chrome.action.setTitle({ title: "Model pull failed" })
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
clearBadge()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
export default defineBackground({
|
||||||
|
main() {
|
||||||
|
chrome.runtime.onMessage.addListener(async (message) => {
|
||||||
|
if (message.type === "sidepanel") {
|
||||||
|
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
||||||
|
const tab = tabs[0]
|
||||||
|
chrome.sidePanel.open({
|
||||||
|
tabId: tab.id!
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if (message.type === "pull_model") {
|
||||||
|
const ollamaURL = await getOllamaURL()
|
||||||
|
|
||||||
|
const isRunning = await isOllamaRunning()
|
||||||
|
|
||||||
|
if (!isRunning) {
|
||||||
|
chrome.action.setBadgeText({ text: "E" })
|
||||||
|
chrome.action.setBadgeBackgroundColor({ color: "#FF0000" })
|
||||||
|
chrome.action.setTitle({ title: "Ollama is not running" })
|
||||||
|
setTimeout(() => {
|
||||||
|
clearBadge()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
await streamDownload(ollamaURL, message.modelName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
chrome.action.onClicked.addListener((tab) => {
|
||||||
|
chrome.tabs.create({ url: chrome.runtime.getURL("options.html") })
|
||||||
|
})
|
||||||
|
|
||||||
|
chrome.commands.onCommand.addListener((command) => {
|
||||||
|
switch (command) {
|
||||||
|
case "execute_side_panel":
|
||||||
|
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
||||||
|
const tab = tabs[0]
|
||||||
|
chrome.sidePanel.open({
|
||||||
|
tabId: tab.id!
|
||||||
|
})
|
||||||
|
})
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
chrome.contextMenus.create({
|
||||||
|
id: "open-side-panel-pa",
|
||||||
|
title: "Open Side Panel to Chat",
|
||||||
|
contexts: ["all"]
|
||||||
|
})
|
||||||
|
|
||||||
|
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||||
|
if (info.menuItemId === "open-side-panel-pa") {
|
||||||
|
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
||||||
|
const tab = tabs[0]
|
||||||
|
await chrome.sidePanel.open({
|
||||||
|
tabId: tab.id!
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
persistent: true
|
||||||
|
})
|
56
src/entries/ollama-pull.content.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
export default defineContentScript({
|
||||||
|
main(ctx) {
|
||||||
|
const downloadModel = async (modelName: string) => {
|
||||||
|
const ok = confirm(
|
||||||
|
`[Page Assist Extension] Do you want to pull ${modelName} model? This has nothing to do with Ollama.com website. The model will be pulled locally once you confirm.`
|
||||||
|
)
|
||||||
|
if (ok) {
|
||||||
|
alert(
|
||||||
|
`[Page Assist Extension] Pulling ${modelName} model. For more details, check the extension icon.`
|
||||||
|
)
|
||||||
|
|
||||||
|
await chrome.runtime.sendMessage({
|
||||||
|
type: "pull_model",
|
||||||
|
modelName
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadSVG = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 w-5 pageasssist-icon">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
const codeDiv = document.querySelectorAll("div.language-none")
|
||||||
|
|
||||||
|
for (let i = 0; i < codeDiv.length; i++) {
|
||||||
|
const button = codeDiv[i].querySelector("button")
|
||||||
|
const command = codeDiv[i].querySelector("input")
|
||||||
|
if (button && command) {
|
||||||
|
const newButton = document.createElement("button")
|
||||||
|
newButton.innerHTML = downloadSVG
|
||||||
|
newButton.className = `border-l ${button.className}`
|
||||||
|
newButton.id = `download-${i}-pageassist`
|
||||||
|
const modelName = command?.value
|
||||||
|
.replace("ollama run", "")
|
||||||
|
.replace("ollama pull", "")
|
||||||
|
.trim()
|
||||||
|
newButton.addEventListener("click", () => {
|
||||||
|
downloadModel(modelName)
|
||||||
|
})
|
||||||
|
|
||||||
|
const span = document.createElement("span")
|
||||||
|
span.title = "Download model via Page Assist"
|
||||||
|
span.appendChild(newButton)
|
||||||
|
|
||||||
|
if (button.parentNode) {
|
||||||
|
button.parentNode.appendChild(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
allFrames: true,
|
||||||
|
matches: ["*://ollama.com/library/*"]
|
||||||
|
})
|
@ -3,11 +3,10 @@ import { MemoryRouter } from "react-router-dom"
|
|||||||
import { ToastContainer } from "react-toastify"
|
import { ToastContainer } from "react-toastify"
|
||||||
import "react-toastify/dist/ReactToastify.css"
|
import "react-toastify/dist/ReactToastify.css"
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
import "./css/tailwind.css"
|
|
||||||
import { ConfigProvider, theme } from "antd"
|
import { ConfigProvider, theme } from "antd"
|
||||||
import { StyleProvider } from "@ant-design/cssinjs"
|
import { StyleProvider } from "@ant-design/cssinjs"
|
||||||
import { useDarkMode } from "~hooks/useDarkmode"
|
import { useDarkMode } from "~/hooks/useDarkmode"
|
||||||
import { OptionRouting } from "~routes"
|
import { OptionRouting } from "~/routes"
|
||||||
function IndexOption() {
|
function IndexOption() {
|
||||||
const { mode } = useDarkMode()
|
const { mode } = useDarkMode()
|
||||||
return (
|
return (
|
14
src/entries/options/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Page Assist - Web UI</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="manifest.type" content="browser_action" />
|
||||||
|
<link href="~/assets/tailwind.css" rel="stylesheet" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body class="bg-white dark:bg-[#171717]">
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="./main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
src/entries/options/main.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import IndexOption from './App';
|
||||||
|
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<IndexOption />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
@ -1,13 +1,12 @@
|
|||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||||||
import { MemoryRouter } from "react-router-dom"
|
import { MemoryRouter } from "react-router-dom"
|
||||||
import { SidepanelRouting } from "~routes"
|
import { SidepanelRouting } from "~/routes"
|
||||||
import { ToastContainer } from "react-toastify"
|
import { ToastContainer } from "react-toastify"
|
||||||
import "react-toastify/dist/ReactToastify.css"
|
import "react-toastify/dist/ReactToastify.css"
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
import "./css/tailwind.css"
|
|
||||||
import { ConfigProvider, theme } from "antd"
|
import { ConfigProvider, theme } from "antd"
|
||||||
import { StyleProvider } from "@ant-design/cssinjs"
|
import { StyleProvider } from "@ant-design/cssinjs"
|
||||||
import { useDarkMode } from "~hooks/useDarkmode"
|
import { useDarkMode } from "~/hooks/useDarkmode"
|
||||||
function IndexSidepanel() {
|
function IndexSidepanel() {
|
||||||
const { mode } = useDarkMode()
|
const { mode } = useDarkMode()
|
||||||
|
|
14
src/entries/sidepanel/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Page Assist - Web UI</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="manifest.type" content="browser_action" />
|
||||||
|
<link href="~/assets/tailwind.css" rel="stylesheet" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body class="bg-white dark:bg-[#171717]">
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="./main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
9
src/entries/sidepanel/main.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from "react"
|
||||||
|
import ReactDOM from "react-dom/client"
|
||||||
|
import IndexSidepanel from "./App"
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<IndexSidepanel />
|
||||||
|
</React.StrictMode>
|
||||||
|
)
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { cleanUrl } from "~libs/clean-url"
|
import { cleanUrl } from "~/libs/clean-url"
|
||||||
import {
|
import {
|
||||||
defaultEmbeddingChunkOverlap,
|
defaultEmbeddingChunkOverlap,
|
||||||
defaultEmbeddingChunkSize,
|
defaultEmbeddingChunkSize,
|
||||||
@ -7,8 +7,8 @@ import {
|
|||||||
getOllamaURL,
|
getOllamaURL,
|
||||||
promptForRag,
|
promptForRag,
|
||||||
systemPromptForNonRag
|
systemPromptForNonRag
|
||||||
} from "~services/ollama"
|
} from "~/services/ollama"
|
||||||
import { useStoreMessage, type ChatHistory, type Message } from "~store"
|
import { useStoreMessage, type ChatHistory, type Message } from "~/store"
|
||||||
import { ChatOllama } from "@langchain/community/chat_models/ollama"
|
import { ChatOllama } from "@langchain/community/chat_models/ollama"
|
||||||
import {
|
import {
|
||||||
HumanMessage,
|
HumanMessage,
|
||||||
@ -16,16 +16,16 @@ import {
|
|||||||
type MessageContent,
|
type MessageContent,
|
||||||
SystemMessage
|
SystemMessage
|
||||||
} from "@langchain/core/messages"
|
} from "@langchain/core/messages"
|
||||||
import { getHtmlOfCurrentTab } from "~libs/get-html"
|
import { getHtmlOfCurrentTab } from "~/libs/get-html"
|
||||||
import { PageAssistHtmlLoader } from "~loader/html"
|
import { PageAssistHtmlLoader } from "~/loader/html"
|
||||||
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
|
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"
|
||||||
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama"
|
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama"
|
||||||
import {
|
import {
|
||||||
createChatWithWebsiteChain,
|
createChatWithWebsiteChain,
|
||||||
groupMessagesByConversation
|
groupMessagesByConversation
|
||||||
} from "~chain/chat-with-website"
|
} from "~/chain/chat-with-website"
|
||||||
import { MemoryVectorStore } from "langchain/vectorstores/memory"
|
import { MemoryVectorStore } from "langchain/vectorstores/memory"
|
||||||
import { chromeRunTime } from "~libs/runtime"
|
import { chromeRunTime } from "~/libs/runtime"
|
||||||
export type BotResponse = {
|
export type BotResponse = {
|
||||||
bot: {
|
bot: {
|
||||||
text: string
|
text: string
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { cleanUrl } from "~libs/clean-url"
|
import { cleanUrl } from "~/libs/clean-url"
|
||||||
import {
|
import {
|
||||||
geWebSearchFollowUpPrompt,
|
geWebSearchFollowUpPrompt,
|
||||||
getOllamaURL,
|
getOllamaURL,
|
||||||
systemPromptForNonRagOption
|
systemPromptForNonRagOption
|
||||||
} from "~services/ollama"
|
} from "~/services/ollama"
|
||||||
import { type ChatHistory, type Message } from "~store/option"
|
import { type ChatHistory, type Message } from "~/store/option"
|
||||||
import { ChatOllama } from "@langchain/community/chat_models/ollama"
|
import { ChatOllama } from "@langchain/community/chat_models/ollama"
|
||||||
import {
|
import {
|
||||||
HumanMessage,
|
HumanMessage,
|
||||||
@ -13,7 +13,7 @@ import {
|
|||||||
type MessageContent,
|
type MessageContent,
|
||||||
SystemMessage
|
SystemMessage
|
||||||
} from "@langchain/core/messages"
|
} from "@langchain/core/messages"
|
||||||
import { useStoreMessageOption } from "~store/option"
|
import { useStoreMessageOption } from "~/store/option"
|
||||||
import {
|
import {
|
||||||
deleteChatForEdit,
|
deleteChatForEdit,
|
||||||
getPromptById,
|
getPromptById,
|
||||||
@ -21,10 +21,10 @@ import {
|
|||||||
saveHistory,
|
saveHistory,
|
||||||
saveMessage,
|
saveMessage,
|
||||||
updateMessageByIndex
|
updateMessageByIndex
|
||||||
} from "~libs/db"
|
} from "~/libs/db"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import { notification } from "antd"
|
import { notification } from "antd"
|
||||||
import { getSystemPromptForWeb } from "~web/web"
|
import { getSystemPromptForWeb } from "~/web/web"
|
||||||
|
|
||||||
export type BotResponse = {
|
export type BotResponse = {
|
||||||
bot: {
|
bot: {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
type ChatHistory as ChatHistoryType,
|
type ChatHistory as ChatHistoryType,
|
||||||
type Message as MessageType
|
type Message as MessageType
|
||||||
} from "~store/option"
|
} from "~/store/option"
|
||||||
|
|
||||||
type HistoryInfo = {
|
type HistoryInfo = {
|
||||||
id: string
|
id: string
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BaseDocumentLoader } from "langchain/document_loaders/base"
|
import { BaseDocumentLoader } from "langchain/document_loaders/base"
|
||||||
import { Document } from "@langchain/core/documents"
|
import { Document } from "@langchain/core/documents"
|
||||||
import { compile } from "html-to-text"
|
import { compile } from "html-to-text"
|
||||||
import { chromeRunTime } from "~libs/runtime"
|
import { chromeRunTime } from "~/libs/runtime"
|
||||||
import { YtTranscript } from "yt-transcript"
|
import { YtTranscript } from "yt-transcript"
|
||||||
|
|
||||||
const YT_REGEX =
|
const YT_REGEX =
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>__plasmo_static_index_title__</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
</head>
|
|
||||||
<body class="bg-white dark:bg-[#171717]"></body>
|
|
||||||
</html>
|
|
BIN
src/public/icon.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src/public/icon/128.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src/public/icon/16.png
Normal file
After Width: | Height: | Size: 345 B |
BIN
src/public/icon/32.png
Normal file
After Width: | Height: | Size: 535 B |
BIN
src/public/icon/48.png
Normal file
After Width: | Height: | Size: 951 B |
BIN
src/public/icon/64.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
@ -1,6 +1,6 @@
|
|||||||
import { Route, Routes } from "react-router-dom"
|
import { Route, Routes } from "react-router-dom"
|
||||||
import { SidepanelChat } from "./sidepanel-chat"
|
import { SidepanelChat } from "./sidepanel-chat"
|
||||||
import { useDarkMode } from "~hooks/useDarkmode"
|
import { useDarkMode } from "~/hooks/useDarkmode"
|
||||||
import { SidepanelSettings } from "./sidepanel-settings"
|
import { SidepanelSettings } from "./sidepanel-settings"
|
||||||
import { OptionIndex } from "./option-index"
|
import { OptionIndex } from "./option-index"
|
||||||
import { OptionModal } from "./option-settings-model"
|
import { OptionModal } from "./option-settings-model"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import OptionLayout from "~components/Layouts/Layout"
|
import OptionLayout from "~/components/Layouts/Layout"
|
||||||
import { Playground } from "~components/Option/Playground/Playground"
|
import { Playground } from "~/components/Option/Playground/Playground"
|
||||||
|
|
||||||
export const OptionIndex = () => {
|
export const OptionIndex = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SettingsLayout } from "~components/Layouts/SettingsOptionLayout"
|
import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
|
||||||
import OptionLayout from "~components/Layouts/Layout"
|
import OptionLayout from "~/components/Layouts/Layout"
|
||||||
import { ModelsBody } from "~components/Option/Models"
|
import { ModelsBody } from "~/components/Option/Models"
|
||||||
|
|
||||||
export const OptionModal = () => {
|
export const OptionModal = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SettingsLayout } from "~components/Layouts/SettingsOptionLayout"
|
import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
|
||||||
import OptionLayout from "~components/Layouts/Layout"
|
import OptionLayout from "~/components/Layouts/Layout"
|
||||||
import { PromptBody } from "~components/Option/Prompt"
|
import { PromptBody } from "~/components/Option/Prompt"
|
||||||
|
|
||||||
export const OptionPrompt = () => {
|
export const OptionPrompt = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SettingsLayout } from "~components/Layouts/SettingsOptionLayout"
|
import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
|
||||||
import OptionLayout from "~components/Layouts/Layout"
|
import OptionLayout from "~/components/Layouts/Layout"
|
||||||
import { OptionShareBody } from "~components/Option/Share"
|
import { OptionShareBody } from "~/components/Option/Share"
|
||||||
|
|
||||||
export const OptionShare = () => {
|
export const OptionShare = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SettingsLayout } from "~components/Layouts/SettingsOptionLayout"
|
import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
|
||||||
import OptionLayout from "~components/Layouts/Layout"
|
import OptionLayout from "~/components/Layouts/Layout"
|
||||||
import { SettingOther } from "~components/Option/Settings/other"
|
import { SettingOther } from "~/components/Option/Settings/other"
|
||||||
|
|
||||||
export const OptionSettings = () => {
|
export const OptionSettings = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SettingsLayout } from "~components/Layouts/SettingsOptionLayout"
|
import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout"
|
||||||
import OptionLayout from "~components/Layouts/Layout"
|
import OptionLayout from "~/components/Layouts/Layout"
|
||||||
import { SettingsOllama } from "~components/Option/Settings/ollama"
|
import { SettingsOllama } from "~/components/Option/Settings/ollama"
|
||||||
|
|
||||||
export const OptionOllamaSettings = () => {
|
export const OptionOllamaSettings = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import { SidePanelBody } from "~components/Sidepanel/Chat/body"
|
import { SidePanelBody } from "~/components/Sidepanel/Chat/body"
|
||||||
import { SidepanelForm } from "~components/Sidepanel/Chat/form"
|
import { SidepanelForm } from "~/components/Sidepanel/Chat/form"
|
||||||
import { SidepanelHeader } from "~components/Sidepanel/Chat/header"
|
import { SidepanelHeader } from "~/components/Sidepanel/Chat/header"
|
||||||
import { useMessage } from "~hooks/useMessage"
|
import { useMessage } from "~/hooks/useMessage"
|
||||||
|
|
||||||
export const SidepanelChat = () => {
|
export const SidepanelChat = () => {
|
||||||
const drop = React.useRef<HTMLDivElement>(null)
|
const drop = React.useRef<HTMLDivElement>(null)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { SettingsBody } from "~components/Sidepanel/Settings/body"
|
import { SettingsBody } from "~/components/Sidepanel/Settings/body"
|
||||||
import { SidepanelSettingsHeader } from "~components/Sidepanel/Settings/header"
|
import { SidepanelSettingsHeader } from "~/components/Sidepanel/Settings/header"
|
||||||
|
|
||||||
export const SidepanelSettings = () => {
|
export const SidepanelSettings = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Storage } from "@plasmohq/storage"
|
import { Storage } from "@plasmohq/storage"
|
||||||
import { cleanUrl } from "~libs/clean-url"
|
import { cleanUrl } from "../libs/clean-url"
|
||||||
import { chromeRunTime } from "~libs/runtime"
|
import { chromeRunTime } from "../libs/runtime"
|
||||||
|
|
||||||
const storage = new Storage()
|
const storage = new Storage()
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { cleanUrl } from "~libs/clean-url"
|
import { cleanUrl } from "~/libs/clean-url"
|
||||||
|
|
||||||
export const verifyPageShareURL = async (url: string) => {
|
export const verifyPageShareURL = async (url: string) => {
|
||||||
const res = await fetch(`${cleanUrl(url)}/api/v1/ping`)
|
const res = await fetch(`${cleanUrl(url)}/api/v1/ping`)
|
||||||
|
@ -2,10 +2,10 @@ 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"
|
||||||
import { MemoryVectorStore } from "langchain/vectorstores/memory"
|
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, getIsSimpleInternetSearch, getOllamaURL } from "~/services/ollama"
|
||||||
|
|
||||||
const BLOCKED_HOSTS = [
|
const BLOCKED_HOSTS = [
|
||||||
"google.com",
|
"google.com",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getWebSearchPrompt } from "~services/ollama"
|
import { getWebSearchPrompt } from "~/services/ollama"
|
||||||
import { webSearch } from "./local-google"
|
import { webSearch } from "./local-google"
|
||||||
|
|
||||||
const getHostName = (url: string) => {
|
const getHostName = (url: string) => {
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"extends": "plasmo/templates/tsconfig.base",
|
"extends": "./.wxt/tsconfig.json",
|
||||||
"exclude": ["node_modules"],
|
|
||||||
"include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.tsx"],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"paths": {
|
"noEmit": true,
|
||||||
"~*": ["./src/*"]
|
"allowImportingTsExtensions": true,
|
||||||
},
|
"allowSyntheticDefaultImports": true,
|
||||||
"baseUrl": "."
|
"esModuleInterop": true,
|
||||||
}
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
}
|
}
|
44
wxt.config.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { defineConfig } from "wxt"
|
||||||
|
import react from "@vitejs/plugin-react"
|
||||||
|
|
||||||
|
// See https://wxt.dev/api/config.html
|
||||||
|
export default defineConfig({
|
||||||
|
vite: () => ({
|
||||||
|
plugins: [react()],
|
||||||
|
}),
|
||||||
|
entrypointsDir: "entries",
|
||||||
|
srcDir: "src",
|
||||||
|
outDir: "build",
|
||||||
|
manifest: {
|
||||||
|
name: "Page Assist - A Web UI for Local AI Models",
|
||||||
|
version: "1.1.0",
|
||||||
|
description:
|
||||||
|
"Use your locally running AI models to assist you in your web browsing.",
|
||||||
|
action: {},
|
||||||
|
author: "n4ze3m",
|
||||||
|
host_permissions: ["http://*/*", "https://*/*"],
|
||||||
|
commands: {
|
||||||
|
_execute_action: {
|
||||||
|
suggested_key: {
|
||||||
|
default: "Ctrl+Shift+L"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
execute_side_panel: {
|
||||||
|
description: "Open the side panel",
|
||||||
|
suggested_key: {
|
||||||
|
default: "Ctrl+Shift+P"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
permissions: [
|
||||||
|
"storage",
|
||||||
|
"sidePanel",
|
||||||
|
"activeTab",
|
||||||
|
"scripting",
|
||||||
|
"declarativeNetRequest",
|
||||||
|
"action",
|
||||||
|
"unlimitedStorage",
|
||||||
|
"contextMenus"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|