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" | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | }) | ||||||