From bfc8c5c8fa96bcf52c8625d1b0912b5367b07065 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Wed, 28 Feb 2024 18:36:17 +0530 Subject: [PATCH 1/8] Update package.json version and fix text area focus bug --- package.json | 2 +- .../Option/Playground/PlaygroundForm.tsx | 23 +++++++++++-------- src/components/Sidepanel/Chat/form.tsx | 19 +++++++++------ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 50f980b..b943152 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pageassist", "displayName": "Page Assist - A Web UI for Local AI Models", - "version": "1.0.4", + "version": "1.0.5", "description": "Use your locally running AI models to assist you in your web browsing.", "author": "n4ze3m", "scripts": { diff --git a/src/components/Option/Playground/PlaygroundForm.tsx b/src/components/Option/Playground/PlaygroundForm.tsx index 459c971..a1a6041 100644 --- a/src/components/Option/Playground/PlaygroundForm.tsx +++ b/src/components/Option/Playground/PlaygroundForm.tsx @@ -19,23 +19,21 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { const textareaRef = React.useRef(null) const inputRef = React.useRef(null) - const resetHeight = () => { - const textarea = textareaRef.current - if (textarea) { - textarea.style.height = "auto" + const textAreaFocus = () => { + if (textareaRef.current) { + textareaRef.current.focus() } } const form = useForm({ initialValues: { message: "", image: "" - } + }, + }) React.useEffect(() => { - if (textareaRef.current) { - textareaRef.current.focus() - } + textAreaFocus() }, []) const onInputChange = async ( @@ -85,9 +83,13 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { const { mutateAsync: sendMessage } = useMutation({ mutationFn: onSubmit, onSuccess: () => { + textAreaFocus() queryClient.invalidateQueries({ queryKey: ["fetchChatHistory"] }) + }, + onError: (error) => { + textAreaFocus() } }) @@ -133,11 +135,12 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { } } form.reset() - resetHeight() + textAreaFocus() await sendMessage({ image: value.image, message: value.message.trim() }) + })} className="shrink-0 flex-grow flex flex-col items-center "> { } } form.reset() - resetHeight() + textAreaFocus() await sendMessage({ image: value.image, message: value.message.trim() diff --git a/src/components/Sidepanel/Chat/form.tsx b/src/components/Sidepanel/Chat/form.tsx index 1c733f9..6e1d820 100644 --- a/src/components/Sidepanel/Chat/form.tsx +++ b/src/components/Sidepanel/Chat/form.tsx @@ -19,10 +19,9 @@ export const SidepanelForm = ({ dropedFile }: Props) => { const inputRef = React.useRef(null) const { sendWhenEnter, setSendWhenEnter } = useWebUI() - const resetHeight = () => { - const textarea = textareaRef.current - if (textarea) { - textarea.style.height = "auto" + const textAreaFocus = () => { + if (textareaRef.current) { + textareaRef.current.focus() } } const form = useForm({ @@ -64,7 +63,13 @@ export const SidepanelForm = ({ dropedFile }: Props) => { } }, [transcript]) const { mutateAsync: sendMessage, isPending: isSending } = useMutation({ - mutationFn: onSubmit + mutationFn: onSubmit, + onSuccess: () => { + textAreaFocus() + }, + onError: (error) => { + textAreaFocus() + } }) return ( @@ -109,7 +114,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => { } } form.reset() - resetHeight() + textAreaFocus() await sendMessage({ image: value.image, message: value.message.trim() @@ -155,7 +160,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => { } } form.reset() - resetHeight() + textAreaFocus() await sendMessage({ image: value.image, message: value.message.trim() From 4365580f5d7e9dd1a0cdefa2b401996fc2a338ba Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Wed, 28 Feb 2024 18:36:25 +0530 Subject: [PATCH 2/8] Refactor SidepanelForm component --- src/components/Sidepanel/Chat/form.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Sidepanel/Chat/form.tsx b/src/components/Sidepanel/Chat/form.tsx index 6e1d820..f8fcd1b 100644 --- a/src/components/Sidepanel/Chat/form.tsx +++ b/src/components/Sidepanel/Chat/form.tsx @@ -216,9 +216,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => { Date: Wed, 28 Feb 2024 22:50:38 +0530 Subject: [PATCH 3/8] Update OptionLayout component and add OptionPrompt route --- src/components/Option/Layout.tsx | 23 ++- src/components/Option/Models/index.tsx | 2 +- src/components/Option/Prompt/index.tsx | 273 +++++++++++++++++++++++++ src/libs/db.ts | 89 ++++++++ src/routes/index.tsx | 2 + src/routes/option-prompt.tsx | 10 + src/web/local-google.ts | 9 +- 7 files changed, 401 insertions(+), 7 deletions(-) create mode 100644 src/components/Option/Prompt/index.tsx create mode 100644 src/routes/option-prompt.tsx diff --git a/src/components/Option/Layout.tsx b/src/components/Option/Layout.tsx index d85a1a3..1fb6b7c 100644 --- a/src/components/Option/Layout.tsx +++ b/src/components/Option/Layout.tsx @@ -4,11 +4,17 @@ import { useLocation, NavLink } from "react-router-dom" import { Sidebar } from "./Sidebar" import { Drawer, Layout, Modal, Select, Tooltip } from "antd" import { useQuery } from "@tanstack/react-query" -import { getAllModels } from "~services/ollama" +import { getAllModels } from "~services/ollama" import { useMessageOption } from "~hooks/useMessageOption" import { Settings } from "./Settings" -import { BrainCircuit, ChevronLeft, CogIcon, GithubIcon, PanelLeftIcon, SquarePen } from "lucide-react" - +import { + Book, + BrainCircuit, + ChevronLeft, + CogIcon, + PanelLeftIcon, + SquarePen +} from "lucide-react" export default function OptionLayout({ children @@ -83,14 +89,21 @@ export default function OptionLayout({
- + + + + + + {/* - + */} {
diff --git a/src/components/Option/Prompt/index.tsx b/src/components/Option/Prompt/index.tsx new file mode 100644 index 0000000..7e328b3 --- /dev/null +++ b/src/components/Option/Prompt/index.tsx @@ -0,0 +1,273 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { + Skeleton, + Table, + Tag, + Tooltip, + notification, + Modal, + Input, + Form, + Switch +} from "antd" +import { Trash2, Pen } from "lucide-react" +import { useState } from "react" +import { + deletePromptById, + getAllPrompts, + savePrompt, + updatePrompt +} from "~libs/db" + +export const PromptBody = () => { + const queryClient = useQueryClient() + const [open, setOpen] = useState(false) + const [openEdit, setOpenEdit] = useState(false) + const [editId, setEditId] = useState("") + const [createForm] = Form.useForm() + const [editForm] = Form.useForm() + + const { data, status } = useQuery({ + queryKey: ["fetchAllPrompts"], + queryFn: getAllPrompts + }) + + const { mutate: deletePrompt } = useMutation({ + mutationFn: deletePromptById, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["fetchAllPrompts"] + }) + notification.success({ + message: "Model Deleted", + description: "Model has been deleted successfully" + }) + }, + onError: (error) => { + notification.error({ + message: "Error", + description: error?.message || "Something went wrong" + }) + } + }) + + const { mutate: savePromptMutation, isPending: savePromptLoading } = + useMutation({ + mutationFn: savePrompt, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["fetchAllPrompts"] + }) + setOpen(false) + createForm.resetFields() + notification.success({ + message: "Prompt Added", + description: "Prompt has been added successfully" + }) + }, + onError: (error) => { + notification.error({ + message: "Error", + description: error?.message || "Something went wrong" + }) + } + }) + + const { mutate: updatePromptMutation, isPending: isUpdatingPrompt } = + useMutation({ + mutationFn: async (data: any) => { + return await updatePrompt({ + ...data, + id: editId + }) + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["fetchAllPrompts"] + }) + setOpenEdit(false) + editForm.resetFields() + notification.success({ + message: "Prompt Updated", + description: "Prompt has been updated successfully" + }) + }, + onError: (error) => { + notification.error({ + message: "Error", + description: error?.message || "Something went wrong" + }) + } + }) + + return ( +
+
+
+
+
+ +
+
+
+ + {status === "pending" && } + + {status === "success" && ( + ( + + {is_system ? "Yes" : "No"} + + ) + }, + { + title: "Action", + render: (_, record) => ( +
+ + + + + + +
+ ) + } + ]} + bordered + dataSource={data} + rowKey={(record) => record.id} + /> + )} + + + setOpen(false)} + footer={null}> +
savePromptMutation(values)} + layout="vertical" + form={createForm}> + + + + + + + + + + + + + + + + +
+ + setOpenEdit(false)} + footer={null}> +
updatePromptMutation(values)} + layout="vertical" + form={editForm}> + + + + + + + + + + + + + + + + +
+ + ) +} diff --git a/src/libs/db.ts b/src/libs/db.ts index ff61f26..75d53a6 100644 --- a/src/libs/db.ts +++ b/src/libs/db.ts @@ -31,10 +31,22 @@ type Message = { createdAt: number } + +type Prompt = { + id: string + title: string + content: string + is_system: boolean + createdBy?: string + createdAt: number +} + type MessageHistory = Message[] type ChatHistory = HistoryInfo[] +type Prompts = Prompt[] + export class PageAssitDatabase { db: chrome.storage.StorageArea @@ -102,8 +114,49 @@ export class PageAssitDatabase { async deleteMessage(history_id: string) { await this.db.remove(history_id) } + + + async getAllPrompts(): Promise { + return new Promise((resolve, reject) => { + this.db.get("prompts", (result) => { + resolve(result.prompts || []) + }) + }) + } + + async addPrompt(prompt: Prompt) { + const prompts = await this.getAllPrompts() + const newPrompts = [prompt, ...prompts] + this.db.set({ prompts: newPrompts }) + } + + async deletePrompt(id: string) { + const prompts = await this.getAllPrompts() + const newPrompts = prompts.filter((prompt) => prompt.id !== id) + this.db.set({ prompts: newPrompts }) + } + + async updatePrompt(id: string, title: string, content: string, is_system: boolean) { + const prompts = await this.getAllPrompts() + const newPrompts = prompts.map((prompt) => { + if (prompt.id === id) { + prompt.title = title + prompt.content = content + prompt.is_system = is_system + } + return prompt + }) + this.db.set({ prompts: newPrompts }) + } + + async getPromptById(id: string) { + const prompts = await this.getAllPrompts() + return prompts.find((prompt) => prompt.id === id) + } + } + const generateID = () => { return "pa_xxxx-xxxx-xxx-xxxx".replace(/[x]/g, () => { const r = Math.floor(Math.random() * 16) @@ -188,3 +241,39 @@ export const removeMessageUsingHistoryId = async (history_id: string) => { const newChatHistory = chatHistory.slice(0, -1) await db.db.set({ [history_id]: newChatHistory }) } + + +export const getAllPrompts = async () => { + const db = new PageAssitDatabase() + return await db.getAllPrompts() +} + + +export const savePrompt = async ({ content, title, is_system = false }: { title: string, content: string, is_system: boolean }) => { + const db = new PageAssitDatabase() + const id = generateID() + const createdAt = Date.now() + const prompt = { id, title, content, is_system, createdAt } + await db.addPrompt(prompt) + return prompt +} + + +export const deletePromptById = async (id: string) => { + const db = new PageAssitDatabase() + await db.deletePrompt(id) + return id +} + + +export const updatePrompt = async ({ content, id, title, is_system }: { id: string, title: string, content: string, is_system: boolean }) => { + const db = new PageAssitDatabase() + await db.updatePrompt(id, title, content, is_system) + return id +} + + +export const getPromptById = async (id: string) => { + const db = new PageAssitDatabase() + return await db.getPromptById(id) +} \ No newline at end of file diff --git a/src/routes/index.tsx b/src/routes/index.tsx index f932315..4abef46 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -4,6 +4,7 @@ import { useDarkMode } from "~hooks/useDarkmode" import { SidepanelSettings } from "./sidepanel-settings" import { OptionIndex } from "./option-index" import { OptionModal } from "./option-model" +import { OptionPrompt } from "./option-prompt" export const OptionRouting = () => { const { mode } = useDarkMode() @@ -13,6 +14,7 @@ export const OptionRouting = () => { } /> } /> + } /> ) diff --git a/src/routes/option-prompt.tsx b/src/routes/option-prompt.tsx new file mode 100644 index 0000000..2fbd433 --- /dev/null +++ b/src/routes/option-prompt.tsx @@ -0,0 +1,10 @@ +import OptionLayout from "~components/Option/Layout" +import { PromptBody } from "~components/Option/Prompt" + +export const OptionPrompt = () => { + return ( + + + + ) +} diff --git a/src/web/local-google.ts b/src/web/local-google.ts index cb2193a..e76475a 100644 --- a/src/web/local-google.ts +++ b/src/web/local-google.ts @@ -40,7 +40,14 @@ export const localGoogleSearch = async (query: string) => { (result) => { const title = result.querySelector("h3")?.textContent const link = result.querySelector("a")?.getAttribute("href") - return { title, link } + let content = result.querySelector("div[data-sncf='2']")?.textContent + if(content === "") { + content = result.querySelector("div[data-sncf='1']")?.textContent + if(content === "") { + content = result.querySelector("div[data-sncf='3']")?.textContent + } + } + return { title, link, content } } ) const filteredSearchResults = searchResults From e0c2c0c745b9ce6955d7d45e366025be3aced052 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sat, 2 Mar 2024 19:50:51 +0530 Subject: [PATCH 4/8] Remove console.log statements and unused dependencies --- package.json | 2 - src/background.ts | 1 - .../Option/Playground/PlaygroundForm.tsx | 77 ++++---- src/components/Sidepanel/Chat/form.tsx | 74 +++---- src/hooks/useMessage.tsx | 2 - src/hooks/useMessageOption.tsx | 3 +- src/hooks/useSpeechRecognition.tsx | 180 +++++++++--------- yarn.lock | 10 - 8 files changed, 167 insertions(+), 182 deletions(-) diff --git a/package.json b/package.json index b943152..1097869 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.10", "@tanstack/react-query": "^5.17.19", - "@types/pdf-parse": "^1.1.4", "antd": "^5.13.3", "axios": "^1.6.7", "dayjs": "^1.11.10", @@ -39,7 +38,6 @@ "rehype-mathjax": "4.0.3", "remark-gfm": "3.0.1", "remark-math": "5.1.1", - "voy-search": "^0.6.3", "zustand": "^4.5.0" }, "devDependencies": { diff --git a/src/background.ts b/src/background.ts index 43b8dde..598d577 100644 --- a/src/background.ts +++ b/src/background.ts @@ -96,7 +96,6 @@ chrome.runtime.onMessage.addListener(async (message) => { clearBadge() }, 5000) } - console.log("Pulling model", message.modelName) await streamDownload(ollamaURL, message.modelName) } diff --git a/src/components/Option/Playground/PlaygroundForm.tsx b/src/components/Option/Playground/PlaygroundForm.tsx index a1a6041..fc6aa4a 100644 --- a/src/components/Option/Playground/PlaygroundForm.tsx +++ b/src/components/Option/Playground/PlaygroundForm.tsx @@ -28,8 +28,7 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { initialValues: { message: "", image: "" - }, - + } }) React.useEffect(() => { @@ -93,6 +92,43 @@ export const PlaygroundForm = ({ dropedFile }: Props) => { } }) + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Process" || e.key === "229") return + if ( + e.key === "Enter" && + !e.shiftKey && + !isSending && + sendWhenEnter && + !e.isComposing + ) { + e.preventDefault() + form.onSubmit(async (value) => { + if (value.message.trim().length === 0) { + return + } + if (!selectedModel || selectedModel.length === 0) { + form.setFieldError("message", "Please select a model") + return + } + if (webSearch) { + const defaultEM = await defaultEmbeddingModelForRag() + if (!defaultEM) { + form.setFieldError( + "message", + "Please set an embedding model on the Settings > Ollama page" + ) + return + } + } + form.reset() + textAreaFocus() + await sendMessage({ + image: value.image, + message: value.message.trim() + }) + })() + } + } return (
{ image: value.image, message: value.message.trim() }) - })} className="shrink-0 flex-grow flex flex-col items-center "> { />