commit
						70fc095edd
					
				| @ -1,23 +1,41 @@ | ||||
| import "katex/dist/katex.min.css" | ||||
| 
 | ||||
| import remarkGfm from "remark-gfm" | ||||
| import remarkMath from "remark-math" | ||||
| import ReactMarkdown from "react-markdown" | ||||
| import rehypeKatex from "rehype-katex" | ||||
| 
 | ||||
| import "property-information" | ||||
| import React from "react" | ||||
| import { CodeBlock } from "./CodeBlock" | ||||
| export const preprocessLaTeX = (content: string) => { | ||||
|   // 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 ( | ||||
|     <React.Fragment> | ||||
|       <ReactMarkdown | ||||
|         className={className} | ||||
|         remarkPlugins={[remarkGfm, remarkMath]} | ||||
|         rehypePlugins={[rehypeKatex]} | ||||
|         components={{ | ||||
|           code({ node, inline, className, children, ...props }) { | ||||
|             const match = /language-(\w+)/.exec(className || "") | ||||
| @ -52,3 +70,5 @@ export default function Markdown({ | ||||
|     </React.Fragment> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| export default Markdown | ||||
|  | ||||
| @ -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<Props> = ({ | ||||
|   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"]}> | ||||
|           <Tooltip title={t("selectAPrompt")}> | ||||
|             <button type="button" className="dark:text-gray-300"> | ||||
|             <button type="button" className={className}> | ||||
|               <BookIcon className="h-5 w-5" /> | ||||
|             </button> | ||||
|           </Tooltip> | ||||
|  | ||||
| @ -45,7 +45,7 @@ export const Header: React.FC<Props> = ({ | ||||
|     setSelectedQuickPrompt, | ||||
|     setSelectedSystemPrompt, | ||||
|     messages, | ||||
|     streaming | ||||
|     streaming, | ||||
|   } = useMessageOption() | ||||
|   const { | ||||
|     data: models, | ||||
| @ -182,7 +182,11 @@ export const Header: React.FC<Props> = ({ | ||||
|           /> | ||||
|         </div> | ||||
|         <div className="lg:hidden"> | ||||
|           <PromptSelect /> | ||||
|           <PromptSelect | ||||
|             selectedSystemPrompt={selectedSystemPrompt} | ||||
|             setSelectedSystemPrompt={setSelectedSystemPrompt} | ||||
|             setSelectedQuickPrompt={setSelectedQuickPrompt} | ||||
|           /> | ||||
|         </div> | ||||
|         <SelectedKnowledge /> | ||||
|       </div> | ||||
|  | ||||
| @ -24,7 +24,6 @@ export const SidepanelForm = ({ dropedFile }: Props) => { | ||||
|   const { sendWhenEnter, setSendWhenEnter } = useWebUI() | ||||
|   const [typing, setTyping] = React.useState<boolean>(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: () => { | ||||
|  | ||||
| @ -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 = () => { | ||||
|             <EraserIcon className="h-5 w-5 text-gray-500 dark:text-gray-400" /> | ||||
|           </button> | ||||
|         )} | ||||
|         {/* <Tooltip title={t("tooltip.history")}> | ||||
|           <Link to="/history"> | ||||
|             <HistoryIcon className="h-5 w-5 text-gray-500 dark:text-gray-400" /> | ||||
|           </Link> | ||||
|         </Tooltip> */} | ||||
|         <PromptSelect | ||||
|           selectedSystemPrompt={selectedSystemPrompt} | ||||
|           setSelectedSystemPrompt={setSelectedSystemPrompt} | ||||
|           setSelectedQuickPrompt={setSelectedQuickPrompt} | ||||
|           className="text-gray-500 dark:text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors" | ||||
|         /> | ||||
| 
 | ||||
|         {!hideCurrentChatModelSettings && ( | ||||
|           <Tooltip title={t("common:currentChatModelSettings")}> | ||||
|             <button | ||||
|  | ||||
| @ -52,5 +52,6 @@ export default defineContentScript({ | ||||
|     } | ||||
|   }, | ||||
|   allFrames: true, | ||||
|   matches: ["*://ollama.com/library/*"] | ||||
|   matches: ["*://ollama.com/*"], | ||||
| 
 | ||||
| }) | ||||
| @ -17,6 +17,7 @@ import { ChatHistory } from "@/store/option" | ||||
| import { | ||||
|   deleteChatForEdit, | ||||
|   generateID, | ||||
|   getPromptById, | ||||
|   removeMessageUsingHistoryId, | ||||
|   updateMessageByIndex | ||||
| } from "@/db" | ||||
| @ -75,9 +76,18 @@ export const useMessage = () => { | ||||
|     setIsEmbedding, | ||||
|     isEmbedding, | ||||
|     currentURL, | ||||
|     setCurrentURL | ||||
|     setCurrentURL, | ||||
|     selectedQuickPrompt, | ||||
|     setSelectedQuickPrompt, | ||||
|     selectedSystemPrompt, | ||||
|     setSelectedSystemPrompt | ||||
|   } = useStoreMessage() | ||||
| 
 | ||||
|   const [speechToTextLanguage, setSpeechToTextLanguage] = useStorage( | ||||
|     "speechToTextLanguage", | ||||
|     "en-US" | ||||
|   ) | ||||
| 
 | ||||
|   const [keepTrackOfEmbedding, setKeepTrackOfEmbedding] = React.useState<{ | ||||
|     [key: string]: MemoryVectorStore | ||||
|   }>({}) | ||||
| @ -488,6 +498,7 @@ export const useMessage = () => { | ||||
| 
 | ||||
|     try { | ||||
|       const prompt = await systemPromptForNonRag() | ||||
|       const selectedPrompt = await getPromptById(selectedSystemPrompt) | ||||
| 
 | ||||
|       let humanMessage = new HumanMessage({ | ||||
|         content: [ | ||||
| @ -514,7 +525,7 @@ export const useMessage = () => { | ||||
| 
 | ||||
|       const applicationChatHistory = generateHistory(history) | ||||
| 
 | ||||
|       if (prompt) { | ||||
|       if (prompt && !selectedPrompt) { | ||||
|         applicationChatHistory.unshift( | ||||
|           new SystemMessage({ | ||||
|             content: [ | ||||
| @ -526,6 +537,18 @@ export const useMessage = () => { | ||||
|           }) | ||||
|         ) | ||||
|       } | ||||
|       if (selectedPrompt) { | ||||
|         applicationChatHistory.unshift( | ||||
|           new SystemMessage({ | ||||
|             content: [ | ||||
|               { | ||||
|                 text: selectedPrompt.content, | ||||
|                 type: "text" | ||||
|               } | ||||
|             ] | ||||
|           }) | ||||
|         ) | ||||
|       } | ||||
| 
 | ||||
|       const chunks = await ollama.stream( | ||||
|         [...applicationChatHistory, humanMessage], | ||||
| @ -1231,6 +1254,12 @@ export const useMessage = () => { | ||||
|     regenerateLastMessage, | ||||
|     webSearch, | ||||
|     setWebSearch, | ||||
|     isSearchingInternet | ||||
|     isSearchingInternet, | ||||
|     selectedQuickPrompt, | ||||
|     setSelectedQuickPrompt, | ||||
|     selectedSystemPrompt, | ||||
|     setSelectedSystemPrompt, | ||||
|     speechToTextLanguage, | ||||
|     setSpeechToTextLanguage | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -55,8 +55,6 @@ export const useMessageOption = () => { | ||||
|     setIsProcessing, | ||||
|     chatMode, | ||||
|     setChatMode, | ||||
|     speechToTextLanguage, | ||||
|     setSpeechToTextLanguage, | ||||
|     webSearch, | ||||
|     setWebSearch, | ||||
|     isSearchingInternet, | ||||
| @ -70,7 +68,10 @@ export const useMessageOption = () => { | ||||
|   } = useStoreMessageOption() | ||||
|   const currentChatModelSettings = useStoreChatModelSettings() | ||||
|   const [selectedModel, setSelectedModel] = useStorage("selectedModel") | ||||
| 
 | ||||
|   const [ speechToTextLanguage, setSpeechToTextLanguage ] = useStorage( | ||||
|     "speechToTextLanguage", | ||||
|     "en-US" | ||||
|   ) | ||||
|   const { ttsEnabled } = useWebUI() | ||||
| 
 | ||||
|   const { t } = useTranslation("option") | ||||
| @ -411,8 +412,6 @@ export const useMessageOption = () => { | ||||
|       const prompt = await systemPromptForNonRagOption() | ||||
|       const selectedPrompt = await getPromptById(selectedSystemPrompt) | ||||
| 
 | ||||
|       // message = message.trim().replaceAll("\n", " ")
 | ||||
| 
 | ||||
|       let humanMessage = new HumanMessage({ | ||||
|         content: [ | ||||
|           { | ||||
|  | ||||
| @ -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<HTMLDivElement>(null); | ||||
|   const [isAtBottom, setIsAtBottom] = useState(true); | ||||
|   const containerRef = useRef<HTMLDivElement>(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 } | ||||
| } | ||||
| @ -329,13 +329,15 @@ export const saveForRag = async ( | ||||
|   chunkSize: number, | ||||
|   overlap: number, | ||||
|   totalFilePerKB: number, | ||||
|   noOfRetrievedDocs: number | ||||
|   noOfRetrievedDocs?: number | ||||
| ) => { | ||||
|   await setDefaultEmbeddingModelForRag(model) | ||||
|   await setDefaultEmbeddingChunkSize(chunkSize) | ||||
|   await setDefaultEmbeddingChunkOverlap(overlap) | ||||
|   await setTotalFilePerKB(totalFilePerKB) | ||||
|   await setNoOfRetrievedDocs(noOfRetrievedDocs) | ||||
|   if(noOfRetrievedDocs) { | ||||
|     await setNoOfRetrievedDocs(noOfRetrievedDocs) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export const getWebSearchPrompt = async () => { | ||||
|  | ||||
| @ -40,6 +40,11 @@ type State = { | ||||
|   setSpeechToTextLanguage: (speechToTextLanguage: string) => void | ||||
|   currentURL: string | ||||
|   setCurrentURL: (currentURL: string) => void | ||||
|   selectedSystemPrompt: string | null | ||||
|   setSelectedSystemPrompt: (selectedSystemPrompt: string) => void | ||||
| 
 | ||||
|   selectedQuickPrompt: string | null | ||||
|   setSelectedQuickPrompt: (selectedQuickPrompt: string) => void | ||||
| } | ||||
| 
 | ||||
| export const useStoreMessage = create<State>((set) => ({ | ||||
| @ -68,5 +73,11 @@ export const useStoreMessage = create<State>((set) => ({ | ||||
|   setSpeechToTextLanguage: (speechToTextLanguage) => | ||||
|     set({ speechToTextLanguage }), | ||||
|   currentURL: "", | ||||
|   setCurrentURL: (currentURL) => set({ currentURL }) | ||||
|   setCurrentURL: (currentURL) => set({ currentURL }), | ||||
| 
 | ||||
|   selectedSystemPrompt: null, | ||||
|   setSelectedSystemPrompt: (selectedSystemPrompt) => | ||||
|     set({ selectedSystemPrompt }), | ||||
|   selectedQuickPrompt: null, | ||||
|   setSelectedQuickPrompt: (selectedQuickPrompt) => set({ selectedQuickPrompt }) | ||||
| })) | ||||
|  | ||||
| @ -62,6 +62,9 @@ type State = { | ||||
| 
 | ||||
|   selectedKnowledge: Knowledge | null | ||||
|   setSelectedKnowledge: (selectedKnowledge: Knowledge) => void | ||||
| 
 | ||||
|   setSpeechToTextLanguage: (language: string) => void | ||||
|   speechToTextLanguage: string | ||||
| } | ||||
| 
 | ||||
| export const useStoreMessageOption = create<State>((set) => ({ | ||||
|  | ||||
| @ -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" | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user