From 824ecf0a8c58c9c2c46189382346de5e7f1b717f Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sun, 8 Sep 2024 11:53:15 +0530 Subject: [PATCH 1/4] feat: Add LaTeX support to the markdown renderer Adds LaTeX support to the markdown renderer using `rehype-katex` for math equations. - Replaces block-level LaTeX delimiters `\[ \]` with `$$ $$`. - Replaces inline LaTeX delimiters `\( \)` with `$ $`. - Preprocesses the message before rendering to ensure correct delimiters. This improves the rendering of markdown messages containing mathematical expressions, enhancing the user experience. --- bun.lockb | Bin 439218 -> 439218 bytes src/components/Common/Markdown.tsx | 22 +++++++++++++- src/hooks/useSmartScroll.tsx | 47 +++++++++++++++++------------ wxt.config.ts | 2 +- 4 files changed, 49 insertions(+), 22 deletions(-) diff --git a/bun.lockb b/bun.lockb index 0669364a49f774ce0ce6b261463eff54484abbb0..546445c09c822c3dbc01c60525679e180948265b 100644 GIT binary patch delta 40 ucmdn=Tx!#EsfHHD7N!>FEiA4r9E@=WdWM#IX6?Q$EI`b<-M57;RUZH>yA5an delta 40 ucmdn=Tx!#EsfHHD7N!>FEiA4r985WhC8@ { + // Replace block-level LaTeX delimiters \[ \] with $$ $$ -export default function Markdown({ + const blockProcessedContent = content.replace( + /\\\[(.*?)\\\]/gs, + (_, equation) => `$$${equation}$$` + ) + // Replace inline LaTeX delimiters \( \) with $ $ + const inlineProcessedContent = blockProcessedContent.replace( + /\\\((.*?)\\\)/gs, + (_, equation) => `$${equation}$` + ) + return inlineProcessedContent +} +function Markdown({ message, className = "prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark" }: { message: string className?: string }) { + message = preprocessLaTeX(message) return ( ) } + +export default React.memo(Markdown) diff --git a/src/hooks/useSmartScroll.tsx b/src/hooks/useSmartScroll.tsx index cad0ce4..d967c29 100644 --- a/src/hooks/useSmartScroll.tsx +++ b/src/hooks/useSmartScroll.tsx @@ -1,35 +1,42 @@ -import { useRef, useEffect, useState } from 'react'; +import { useRef, useEffect, useState } from "react" export const useSmartScroll = (messages: any[], streaming: boolean) => { - const containerRef = useRef(null); - const [isAtBottom, setIsAtBottom] = useState(true); + const containerRef = useRef(null) + const [isAtBottom, setIsAtBottom] = useState(true) useEffect(() => { - const container = containerRef.current; - if (!container) return; + const container = containerRef.current + if (!container) return const handleScroll = () => { - const { scrollTop, scrollHeight, clientHeight } = container; - setIsAtBottom(scrollHeight - scrollTop - clientHeight < 50); - }; + const { scrollTop, scrollHeight, clientHeight } = container + setIsAtBottom(scrollHeight - scrollTop - clientHeight < 50) + } - container.addEventListener('scroll', handleScroll); - return () => container.removeEventListener('scroll', handleScroll); - }, []); + container.addEventListener("scroll", handleScroll) + return () => container.removeEventListener("scroll", handleScroll) + }, []) useEffect(() => { + if (messages.length === 0) { + setIsAtBottom(true) + return + } + if (isAtBottom && containerRef.current) { const scrollOptions: ScrollIntoViewOptions = streaming - ? { behavior: 'smooth', block: 'end' } - : { behavior: 'auto', block: 'end' }; - containerRef.current.lastElementChild?.scrollIntoView(scrollOptions); + ? { behavior: "smooth", block: "end" } + : { behavior: "auto", block: "end" } + containerRef.current.lastElementChild?.scrollIntoView(scrollOptions) } - }, [messages, streaming, isAtBottom]); + }, [messages, streaming, isAtBottom]) const scrollToBottom = () => { - containerRef.current?.lastElementChild?.scrollIntoView({ behavior: 'smooth', block: 'end' }); - }; + containerRef.current?.lastElementChild?.scrollIntoView({ + behavior: "smooth", + block: "end" + }) + } - - return { containerRef, isAtBottom, scrollToBottom }; -}; + return { containerRef, isAtBottom, scrollToBottom } +} \ No newline at end of file diff --git a/wxt.config.ts b/wxt.config.ts index 3a88ec8..6ad0b4e 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -50,7 +50,7 @@ export default defineConfig({ outDir: "build", manifest: { - version: "1.2.2", + version: "1.2.3", name: process.env.TARGET === "firefox" ? "Page Assist - A Web UI for Local AI Models" From e44c47905a0048e5931157bc494bd914afb4527a Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sun, 8 Sep 2024 20:52:30 +0530 Subject: [PATCH 2/4] feat: Remove unnecessary memoization --- src/components/Common/Markdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Common/Markdown.tsx b/src/components/Common/Markdown.tsx index 960b3bd..2934b64 100644 --- a/src/components/Common/Markdown.tsx +++ b/src/components/Common/Markdown.tsx @@ -71,4 +71,4 @@ function Markdown({ ) } -export default React.memo(Markdown) +export default Markdown From 5602714ee2d2870f4798e4bcf2eebfcea636a9b9 Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sat, 14 Sep 2024 11:08:32 +0530 Subject: [PATCH 3/4] Fix: Extend content script scope to all of ollama.com --- src/entries/ollama-pull.content.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/entries/ollama-pull.content.ts b/src/entries/ollama-pull.content.ts index 6acbccd..89250e3 100644 --- a/src/entries/ollama-pull.content.ts +++ b/src/entries/ollama-pull.content.ts @@ -52,5 +52,6 @@ export default defineContentScript({ } }, allFrames: true, - matches: ["*://ollama.com/library/*"] + matches: ["*://ollama.com/*"], + }) \ No newline at end of file From 53d999a596a0f0c9e9a644260ef47b4269a7524e Mon Sep 17 00:00:00 2001 From: n4ze3m Date: Sat, 14 Sep 2024 12:07:54 +0530 Subject: [PATCH 4/4] feat: Add system and quick prompts on side panel --- src/components/Common/PromptSelect.tsx | 23 ++++++++++------ src/components/Layouts/Header.tsx | 8 ++++-- src/components/Sidepanel/Chat/form.tsx | 26 +++++++++++++++--- src/components/Sidepanel/Chat/header.tsx | 23 ++++++++++++---- src/hooks/useMessage.tsx | 35 ++++++++++++++++++++++-- src/hooks/useMessageOption.tsx | 9 +++--- src/services/ollama.ts | 6 ++-- src/store/index.tsx | 13 ++++++++- src/store/option.tsx | 3 ++ 9 files changed, 115 insertions(+), 31 deletions(-) diff --git a/src/components/Common/PromptSelect.tsx b/src/components/Common/PromptSelect.tsx index fadd8c0..dd493a9 100644 --- a/src/components/Common/PromptSelect.tsx +++ b/src/components/Common/PromptSelect.tsx @@ -4,20 +4,27 @@ import { BookIcon, ComputerIcon, ZapIcon } from "lucide-react" import React from "react" import { useTranslation } from "react-i18next" import { getAllPrompts } from "@/db" -import { useMessageOption } from "@/hooks/useMessageOption" -export const PromptSelect: React.FC = () => { +type Props = { + setSelectedSystemPrompt: (promptId: string | undefined) => void + setSelectedQuickPrompt: (prompt: string | undefined) => void + selectedSystemPrompt: string | undefined + className?: string +} + +export const PromptSelect: React.FC = ({ + setSelectedQuickPrompt, + setSelectedSystemPrompt, + selectedSystemPrompt, + className = "dark:text-gray-300" +}) => { const { t } = useTranslation("option") - const { - selectedSystemPrompt, - setSelectedQuickPrompt, - setSelectedSystemPrompt - } = useMessageOption() const { data } = useQuery({ queryKey: ["getAllPromptsForSelect"], queryFn: getAllPrompts }) + const handlePromptChange = (value?: string) => { if (!value) { setSelectedSystemPrompt(undefined) @@ -79,7 +86,7 @@ export const PromptSelect: React.FC = () => { placement={"topLeft"} trigger={["click"]}> - diff --git a/src/components/Layouts/Header.tsx b/src/components/Layouts/Header.tsx index f7251b1..65fab8e 100644 --- a/src/components/Layouts/Header.tsx +++ b/src/components/Layouts/Header.tsx @@ -45,7 +45,7 @@ export const Header: React.FC = ({ setSelectedQuickPrompt, setSelectedSystemPrompt, messages, - streaming + streaming, } = useMessageOption() const { data: models, @@ -182,7 +182,11 @@ export const Header: React.FC = ({ />
- +
diff --git a/src/components/Sidepanel/Chat/form.tsx b/src/components/Sidepanel/Chat/form.tsx index c413377..e1cc3f0 100644 --- a/src/components/Sidepanel/Chat/form.tsx +++ b/src/components/Sidepanel/Chat/form.tsx @@ -24,7 +24,6 @@ export const SidepanelForm = ({ dropedFile }: Props) => { const { sendWhenEnter, setSendWhenEnter } = useWebUI() const [typing, setTyping] = React.useState(false) const { t } = useTranslation(["playground", "common"]) - const form = useForm({ initialValues: { message: "", @@ -37,7 +36,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => { resetTranscript, start: startListening, stop: stopSpeechRecognition, - supported: browserSupportsSpeechRecognition + supported: browserSupportsSpeechRecognition, } = useSpeechRecognition() const stopListening = async () => { @@ -118,12 +117,14 @@ export const SidepanelForm = ({ dropedFile }: Props) => { onSubmit, selectedModel, chatMode, - speechToTextLanguage, stopStreamingRequest, streaming, setChatMode, webSearch, - setWebSearch + setWebSearch, + selectedQuickPrompt, + setSelectedQuickPrompt, + speechToTextLanguage } = useMessage() React.useEffect(() => { @@ -139,6 +140,23 @@ export const SidepanelForm = ({ dropedFile }: Props) => { form.setFieldValue("message", transcript) } }, [transcript]) + + React.useEffect(() => { + if (selectedQuickPrompt) { + const word = getVariable(selectedQuickPrompt) + form.setFieldValue("message", selectedQuickPrompt) + if (word) { + textareaRef.current?.focus() + const interval = setTimeout(() => { + textareaRef.current?.setSelectionRange(word.start, word.end) + setSelectedQuickPrompt(null) + }, 100) + return () => { + clearInterval(interval) + } + } + } + }, [selectedQuickPrompt]) const { mutateAsync: sendMessage, isPending: isSending } = useMutation({ mutationFn: onSubmit, onSuccess: () => { diff --git a/src/components/Sidepanel/Chat/header.tsx b/src/components/Sidepanel/Chat/header.tsx index 5970a7c..95e1374 100644 --- a/src/components/Sidepanel/Chat/header.tsx +++ b/src/components/Sidepanel/Chat/header.tsx @@ -7,13 +7,22 @@ import { useTranslation } from "react-i18next" import { CurrentChatModelSettings } from "@/components/Common/Settings/CurrentChatModelSettings" import React from "react" import { useStorage } from "@plasmohq/storage/hook" +import { PromptSelect } from "@/components/Common/PromptSelect" export const SidepanelHeader = () => { const [hideCurrentChatModelSettings] = useStorage( "hideCurrentChatModelSettings", false ) - const { clearChat, isEmbedding, messages, streaming } = useMessage() + const { + clearChat, + isEmbedding, + messages, + streaming, + selectedSystemPrompt, + setSelectedSystemPrompt, + setSelectedQuickPrompt + } = useMessage() const { t } = useTranslation(["sidepanel", "common"]) const [openModelSettings, setOpenModelSettings] = React.useState(false) @@ -44,11 +53,13 @@ export const SidepanelHeader = () => { )} - {/* - - - - */} + + {!hideCurrentChatModelSettings && (