Add deleteChatHistory method to PageAssitDatabase class and systemPromptForNonRagOption functions to ollama service

This commit is contained in:
n4ze3m 2024-02-07 21:07:41 +05:30
parent c1efb2d5cb
commit b54527cab5
14 changed files with 393 additions and 66 deletions

View File

@ -1,8 +1,8 @@
{
"name": "pageassist",
"displayName": "Page Assist - AI Powered Browser Assistant",
"version": "0.0.1",
"description": "Use your local AI models to assist you in your daily browsing.",
"displayName": "Page Assist - Browse the Internet with Your Local AI Models",
"version": "1.0.0",
"description": "Page Assist is a browser extension that allows you to browse the internet with your local AI models. It is a privacy-focused and open-source project.",
"author": "n4ze3m",
"scripts": {
"dev": "plasmo dev",
@ -55,32 +55,30 @@
},
"manifest": {
"host_permissions": [
"https://*/*",
"http://*/*",
"http://*:11434/api/tags",
"http://*:11434/api/chat",
"https://*:11434/api/tags",
"https://*:11434/api/chat"
"https://*/*"
],
"web_accessible_resources": [
{
"resources": [
"popup.html"
],
"matches": [
"https://*/*",
"http://*/*"
]
"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",
"declarativeNetRequestFeedback",
"action",
"unlimitedStorage"
"unlimitedStorage",
"contextMenus"
]
}
}
}

View File

@ -12,5 +12,37 @@ chrome.runtime.onMessage.addListener(async (message) => {
})
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.create({url: chrome.runtime.getURL("options.html")});
});
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
})
})
}
})

View File

@ -15,11 +15,13 @@ import logoImage from "data-base64:~assets/icon.png"
import { Link, useParams, useLocation, useNavigate } from "react-router-dom"
import { Sidebar } from "./Sidebar"
import { Drawer, Layout, Select } from "antd"
import { Drawer, Layout, Modal, Select } from "antd"
import { useQuery } from "@tanstack/react-query"
import { fetchModels } from "~services/ollama"
import { useMessageOption } from "~hooks/useMessageOption"
import { PanelLeftIcon, Settings2 } from "lucide-react"
import { Settings } from "./Settings"
import { useDarkMode } from "~hooks/useDarkmode"
const navigation = [
{ name: "Embed", href: "/bot/:id", icon: TagIcon },
@ -51,8 +53,7 @@ export default function OptionLayout({
children: React.ReactNode
}) {
const [sidebarOpen, setSidebarOpen] = useState(false)
const params = useParams<{ id: string }>()
const location = useLocation()
const [open, setOpen] = useState(false)
const {
data: models,
@ -66,9 +67,10 @@ export default function OptionLayout({
const { selectedModel, setSelectedModel } = useMessageOption()
return (
<Layout className="bg-white dark:bg-[#171717] md:flex">
<div className="flex items-center p-3 fixed flex-row justify-between border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-[#171717] w-full z-10">
<div className="flex items-center p-3 fixed flex-row justify-between border-b border-gray-200 dark:border-gray-600 bg-white dark:bg-[#171717] w-full z-10">
<div className="flex items-center flex-row gap-3">
<div>
<button
@ -92,7 +94,12 @@ export default function OptionLayout({
/>
</div>
</div>
<button className="text-gray-500 dark:text-gray-400">
<button>
</button>
<button
onClick={() => setOpen(true)}
className="text-gray-500 dark:text-gray-400">
<CogIcon className="w-6 h-6" />
</button>
</div>
@ -104,10 +111,19 @@ export default function OptionLayout({
placement="left"
closeIcon={null}
onClose={() => setSidebarOpen(false)}
open={sidebarOpen}
>
open={sidebarOpen}>
<Sidebar />
</Drawer>
<Modal
open={open}
width={800}
title={"Settings"}
onOk={() => setOpen(false)}
footer={null}
onCancel={() => setOpen(false)}>
<Settings setClose={() => setOpen(false)} />
</Modal>
</Layout>
)
}

View File

@ -2,6 +2,7 @@ import React from "react"
import { useMessage } from "~hooks/useMessage"
import { useMessageOption } from "~hooks/useMessageOption"
import { PlaygroundMessage } from "./PlaygroundMessage"
import { PlaygroundEmpty } from "./PlaygroundEmpty"
export const PlaygroundChat = () => {
const { messages } = useMessageOption()
@ -13,7 +14,11 @@ export const PlaygroundChat = () => {
})
return (
<div className="grow flex flex-col md:translate-x-0 transition-transform duration-300 ease-in-out">
{/* {messages.length === 0 && <div>no message</div>} */}
{messages.length === 0 && (
<div className="mt-32">
<PlaygroundEmpty />
</div>
)}
{messages.length > 0 && <div className="w-full h-14 flex-shrink-0"></div>}
{messages.map((message, index) => (
<PlaygroundMessage

View File

@ -0,0 +1,86 @@
import { useQuery } from "@tanstack/react-query"
import { useEffect, useState } from "react"
import { useMessage } from "~hooks/useMessage"
import { useMessageOption } from "~hooks/useMessageOption"
import {
getOllamaURL,
isOllamaRunning,
setOllamaURL as saveOllamaURL
} from "~services/ollama"
export const PlaygroundEmpty = () => {
const [ollamaURL, setOllamaURL] = useState<string>("")
const {
data: ollamaInfo,
status: ollamaStatus,
refetch,
isRefetching
} = useQuery({
queryKey: ["ollamaStatus"],
queryFn: async () => {
const ollamaURL = await getOllamaURL()
const isOk = await isOllamaRunning()
return {
isOk,
ollamaURL
}
}
})
useEffect(() => {
if (ollamaInfo?.ollamaURL) {
setOllamaURL(ollamaInfo.ollamaURL)
}
}, [ollamaInfo])
return (
<div className="mx-auto sm:max-w-xl px-4 mt-10">
<div className="rounded-lg justify-center items-center flex flex-col border p-8 bg-white dark:bg-[#262626] shadow-sm dark:border-gray-600">
{(ollamaStatus === "pending" || isRefetching) && (
<div className="inline-flex items-center space-x-2">
<div className="w-3 h-3 bg-blue-500 rounded-full animate-bounce"></div>
<p className="dark:text-gray-400 text-gray-900">
Searching for Your Ollama 🦙
</p>
</div>
)}
{!isRefetching && ollamaStatus === "success" ? (
ollamaInfo.isOk ? (
<div className="inline-flex items-center space-x-2">
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
<p className="dark:text-gray-400 text-gray-900">
Ollama is running 🦙
</p>
</div>
) : (
<div className="flex flex-col space-y-2 justify-center items-center">
<div className="inline-flex space-x-2">
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
<p className="dark:text-gray-400 text-gray-900">
Unable to connect to Ollama 🦙
</p>
</div>
<input
className="bg-gray-100 dark:bg-[#262626] dark:text-gray-100 rounded-md px-4 py-2 mt-2 w-full"
type="url"
value={ollamaURL}
onChange={(e) => setOllamaURL(e.target.value)}
/>
<button
onClick={() => {
saveOllamaURL(ollamaURL)
refetch()
}}
className="bg-pink-500 mt-4 hover:bg-pink-600 text-white px-4 py-2 rounded-md dark:bg-pink-600 dark:hover:bg-pink-700">
Retry
</button>
</div>
)
) : null}
</div>
</div>
)
}

View File

@ -66,7 +66,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
})
return (
<div className="p-3 md:p-6 md:bg-white dark:bg-black border rounded-t-xl border-black/10 dark:border-gray-800">
<div className="p-3 md:p-6 md:bg-white dark:bg-[#262626] border rounded-t-xl border-black/10 dark:border-gray-600">
<div className="flex-grow space-y-6 ">
<div
className={`h-full rounded-md shadow relative ${
@ -82,7 +82,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
onClick={() => {
form.setFieldValue("image", "")
}}
className="absolute top-2 right-2 bg-white dark:bg-black p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-600 text-black dark:text-gray-100">
className="absolute top-2 right-2 bg-white dark:bg-[#262626] p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-600 text-black dark:text-gray-100">
<XMarkIcon className="h-5 w-5" />
</button>
</div>
@ -109,7 +109,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
})
})}
className="shrink-0 flex-grow flex items-center ">
<div className="flex items-center p-2 rounded-2xl border bg-gray-100 w-full dark:bg-black dark:border-gray-800">
<div className="flex items-center p-2 rounded-2xl border bg-gray-100 w-full dark:bg-[#262626] dark:border-gray-600">
<button
type="button"
onClick={() => {
@ -162,7 +162,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => {
/>
<button
disabled={isSending || form.values.message.length === 0}
className="ml-2 flex items-center justify-center w-10 h-10 text-white bg-black rounded-xl disabled:opacity-50">
className="ml-2 flex items-center justify-center w-10 h-10 text-white bg-[#262626] rounded-xl disabled:opacity-50">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"

View File

@ -0,0 +1,39 @@
import { Tabs } from "antd"
import { SettingsOllama } from "./Settings/ollama"
import { SettingPrompt } from "./Settings/prompt"
import { SettingOther } from "./Settings/other"
type Props = {
setClose: (close: boolean) => void
}
export const Settings = ({ setClose }: Props) => {
return (
<div className="my-6">
<Tabs
tabPosition="left"
defaultActiveKey="1"
items={[
{
id: "1",
key: "1",
label: "Prompt",
children: <SettingPrompt />
},
{
id: "2",
key: "2",
label: "Ollama",
children: <SettingsOllama />
},
{
id: "3",
key: "3",
label: "Other",
children: <SettingOther />
}
]}
/>
</div>
)
}

View File

@ -0,0 +1,55 @@
import { useQuery } from "@tanstack/react-query"
import { useEffect, useState } from "react"
import { SaveButton } from "~components/Common/SaveButton"
import { getOllamaURL, setOllamaURL as saveOllamaURL } from "~services/ollama"
export const SettingsOllama = () => {
const [ollamaURL, setOllamaURL] = useState<string>("")
const { data: ollamaInfo } = useQuery({
queryKey: ["fetchOllamURL"],
queryFn: async () => {
const ollamaURL = await getOllamaURL()
return {
ollamaURL
}
}
})
useEffect(() => {
if (ollamaInfo?.ollamaURL) {
setOllamaURL(ollamaInfo.ollamaURL)
}
}, [ollamaInfo])
return (
<div className="">
<div>
<label
htmlFor="ollamaURL"
className="text-sm font-medium dark:text-gray-200">
Ollama URL
</label>
<input
type="url"
id="ollamaURL"
value={ollamaURL}
onChange={(e) => {
setOllamaURL(e.target.value)
}}
placeholder="Your Ollama URL"
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
/>
</div>
<div className="flex justify-end">
<SaveButton
onClick={() => {
saveOllamaURL(ollamaURL)
}}
className="mt-2"
/>
</div>
</div>
)
}

View File

@ -0,0 +1,52 @@
import { useQueryClient } from "@tanstack/react-query"
import { useDarkMode } from "~hooks/useDarkmode"
import { useMessageOption } from "~hooks/useMessageOption"
import { PageAssitDatabase } from "~libs/db"
export const SettingOther = () => {
const { clearChat } = useMessageOption()
const queryClient = useQueryClient()
const { mode, toggleDarkMode } = useDarkMode()
return (
<div className="flex flex-col space-y-4">
<div className="flex flex-row justify-between">
<span className="text-gray-500 dark:text-gray-400 text-lg">
Change Theme
</span>
<button
onClick={toggleDarkMode}
className="bg-blue-500 dark:bg-blue-600 text-white dark:text-gray-200 px-4 py-2 rounded-md">
{mode === "dark" ? "Light" : "Dark"}
</button>
</div>
<div className="flex flex-row justify-between">
<span className="text-gray-500 dark:text-gray-400 text-lg">
Delete Chat History
</span>
<button
onClick={async () => {
const confirm = window.confirm(
"Are you sure you want to delete your chat history? This action cannot be undone."
)
if (confirm) {
const db = new PageAssitDatabase()
await db.deleteChatHistory()
queryClient.invalidateQueries({
queryKey: ["fetchChatHistory"]
})
clearChat()
}
}}
className="bg-red-500 dark:bg-red-600 text-white dark:text-gray-200 px-4 py-2 rounded-md">
Delete
</button>
</div>
</div>
)
}

View File

@ -0,0 +1,56 @@
import { useQuery } from "@tanstack/react-query"
import { useEffect, useState } from "react"
import { SaveButton } from "~components/Common/SaveButton"
import {
setSystemPromptForNonRagOption,
systemPromptForNonRagOption
} from "~services/ollama"
export const SettingPrompt = () => {
const [ollamaPrompt, setOllamaPrompt] = useState<string>("")
const { data: ollamaInfo } = useQuery({
queryKey: ["fetchOllaPrompt"],
queryFn: async () => {
const prompt = await systemPromptForNonRagOption()
return {
prompt
}
}
})
useEffect(() => {
if (ollamaInfo?.prompt) {
setOllamaPrompt(ollamaInfo.prompt)
}
}, [ollamaInfo])
return (
<div className="">
<div>
<label htmlFor="ollamaPrompt" className="text-sm font-medium dark:text-gray-200">
System Prompt
</label>
<textarea
value={ollamaPrompt}
rows={5}
id="ollamaPrompt"
placeholder="Your System Prompt"
onChange={(e) => {
setOllamaPrompt(e.target.value)
}}
className="w-full p-2 border border-gray-300 rounded-md dark:bg-[#262626] dark:text-gray-100"
/>
</div>
<div className="flex justify-end">
<SaveButton
onClick={() => {
setSystemPromptForNonRagOption(ollamaPrompt)
}}
className="mt-2"
/>
</div>
</div>
)
}

View File

@ -1,29 +0,0 @@
export {}
import { Storage } from "@plasmohq/storage"
const storage = new Storage()
const sidePanelController = async () => {
const sidepanelCommand = await storage.get("sidepanel-command")
const command = sidepanelCommand || "Ctrl+0"
document.addEventListener("keydown", (event) => {
let pressedKey = ""
if (event.ctrlKey) {
pressedKey += "Ctrl+"
}
if (event.shiftKey) {
pressedKey += "Shift+"
}
pressedKey += event.key
if (pressedKey === command) {
chrome.runtime.sendMessage({ type: "sidepanel" })
}
})
}
sidePanelController()

View File

@ -1,6 +1,6 @@
import React from "react"
import { cleanUrl } from "~libs/clean-url"
import { getOllamaURL, systemPromptForNonRag } from "~services/ollama"
import { getOllamaURL, systemPromptForNonRagOption } from "~services/ollama"
import { type ChatHistory, type Message } from "~store/option"
import { ChatOllama } from "@langchain/community/chat_models/ollama"
import {
@ -139,7 +139,7 @@ export const useMessageOption = () => {
setMessages(newMessage)
try {
const prompt = await systemPromptForNonRag()
const prompt = await systemPromptForNonRagOption()
message = message.trim().replaceAll("\n", " ")

View File

@ -79,6 +79,14 @@ export class PageAssitDatabase {
async clear() {
this.db.clear()
}
async deleteChatHistory() {
const chatHistories = await this.getChatHistories()
for (const history of chatHistories) {
this.db.remove(history.id)
}
this.db.remove("chatHistories")
}
}
const generateID = () => {

View File

@ -121,3 +121,12 @@ export const setPromptForRag = async (
await storage.set("systemPromptForRag", prompt)
await storage.set("questionPromptForRag", questionPrompt)
}
export const systemPromptForNonRagOption = async () => {
const prompt = await storage.get("systemPromptForNonRagOption")
return prompt
}
export const setSystemPromptForNonRagOption = async (prompt: string) => {
await storage.set("systemPromptForNonRagOption", prompt)
}