Add Sidepanel Settings Body component and update button styles in EmptySidePanel component
This commit is contained in:
parent
be3a4ed256
commit
5958e10354
@ -3,6 +3,7 @@
|
||||
A simple browser extension to assist you in talking with the current page, along with a web UI for the [Ollama](https://github.com/ollama/ollama) project.
|
||||
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- [X] Fully local, no data is sent to any server
|
||||
@ -13,6 +14,10 @@ A simple browser extension to assist you in talking with the current page, along
|
||||
- [ ] Other Local AI providers
|
||||
|
||||
|
||||
## V1
|
||||
|
||||
If you are looking for the V1 of this project, you can find it on v1 branch. I created it as a hackathon project and it is not maintained anymore.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
@ -79,15 +79,17 @@ export const PlaygroundMessage = (props: Props) => {
|
||||
</div>
|
||||
{props.images && (
|
||||
<div className="flex md:max-w-2xl lg:max-w-xl xl:max-w-3xl p-3 m-auto w-full">
|
||||
{props.images.map((image, index) => (
|
||||
<div key={index} className="h-full rounded-md shadow relative">
|
||||
<img
|
||||
src={image}
|
||||
alt="Uploaded"
|
||||
className="h-full w-auto object-cover rounded-md min-w-[50px]"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{props.images
|
||||
.filter((image) => image.length > 0)
|
||||
.map((image, index) => (
|
||||
<div key={index} className="h-full rounded-md shadow relative">
|
||||
<img
|
||||
src={image}
|
||||
alt="Uploaded"
|
||||
className="h-full w-auto object-cover rounded-md min-w-[50px]"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -79,7 +79,7 @@ export const EmptySidePanel = () => {
|
||||
saveOllamaURL(ollamaURL)
|
||||
refetch()
|
||||
}}
|
||||
className="bg-blue-500 mt-4 hover:bg-blue-600 text-white px-4 py-2 rounded-md">
|
||||
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>
|
||||
|
@ -57,7 +57,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="p-3 md:p-6 md:bg-white dark:bg-[#0a0a0a] border rounded-t-xl border-black/10 dark:border-gray-900/50">
|
||||
<div className="p-3 md:p-6 md:bg-white dark:bg-[#1a1919] border rounded-t-xl border-black/10 dark:border-gray-900/50">
|
||||
<div className="flex-grow space-y-6 ">
|
||||
{chatMode === "normal" && form.values.image && (
|
||||
<div className="h-full rounded-md shadow relative">
|
||||
@ -136,7 +136,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
|
||||
}
|
||||
}}
|
||||
ref={textareaRef}
|
||||
className="pl-4 pr-2 py-2 w-full resize-none bg-transparent focus-within:outline-none sm:text-sm focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"
|
||||
className="px-2 py-2 w-full resize-none bg-transparent focus-within:outline-none sm:text-sm focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"
|
||||
required
|
||||
rows={1}
|
||||
tabIndex={0}
|
||||
@ -145,7 +145,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
|
||||
/>
|
||||
<button
|
||||
disabled={isSending || form.values.message.length === 0}
|
||||
className="mx-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-black rounded-xl disabled:opacity-50">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
|
177
src/components/Sidepanel/Settings/body.tsx
Normal file
177
src/components/Sidepanel/Settings/body.tsx
Normal file
@ -0,0 +1,177 @@
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import React from "react"
|
||||
import {
|
||||
getOllamaURL,
|
||||
systemPromptForNonRag,
|
||||
promptForRag,
|
||||
setOllamaURL as saveOllamaURL,
|
||||
setPromptForRag,
|
||||
setSystemPromptForNonRag
|
||||
} from "~services/ollama"
|
||||
|
||||
import { Skeleton, Radio } from "antd"
|
||||
import { useDarkMode } from "~hooks/useDarkmode"
|
||||
|
||||
export const SettingsBody = () => {
|
||||
const [ollamaURL, setOllamaURL] = React.useState<string>("")
|
||||
const [systemPrompt, setSystemPrompt] = React.useState<string>("")
|
||||
const [ragPrompt, setRagPrompt] = React.useState<string>("")
|
||||
const [ragQuestionPrompt, setRagQuestionPrompt] = React.useState<string>("")
|
||||
const [selectedValue, setSelectedValue] = React.useState<"normal" | "rag">(
|
||||
"normal"
|
||||
)
|
||||
const { mode, toggleDarkMode } = useDarkMode()
|
||||
|
||||
const { data, status } = useQuery({
|
||||
queryKey: ["sidebarSettings"],
|
||||
queryFn: async () => {
|
||||
const [ollamaURL, systemPrompt, ragPrompt] = await Promise.all([
|
||||
getOllamaURL(),
|
||||
systemPromptForNonRag(),
|
||||
promptForRag()
|
||||
])
|
||||
|
||||
return {
|
||||
url: ollamaURL,
|
||||
normalSystemPrompt: systemPrompt,
|
||||
ragSystemPrompt: ragPrompt.ragPrompt,
|
||||
ragQuestionPrompt: ragPrompt.ragQuestionPrompt
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
if (data) {
|
||||
setOllamaURL(data.url)
|
||||
setSystemPrompt(data.normalSystemPrompt)
|
||||
setRagPrompt(data.ragSystemPrompt)
|
||||
setRagQuestionPrompt(data.ragQuestionPrompt)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
if (status === "pending") {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<Skeleton active />
|
||||
<Skeleton active />
|
||||
<Skeleton active />
|
||||
<Skeleton active />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (status === "error") {
|
||||
return <div>Error</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-4 max-w-2xl mx-auto lg:max-w-3xl xl:max-w-4xl 2xl:max-w-5xl">
|
||||
<div className="border border-gray-300 dark:border-gray-700 rounded p-4">
|
||||
<h2 className="text-md mb-4 font-semibold dark:text-white">
|
||||
Ollama URL
|
||||
</h2>
|
||||
<input
|
||||
className="w-full border border-gray-300 dark:border-gray-700 rounded p-2 dark:bg-black dark:text-white dark:placeholder-gray-400"
|
||||
value={ollamaURL}
|
||||
type="url"
|
||||
onChange={(e) => setOllamaURL(e.target.value)}
|
||||
placeholder="Enter Ollama URL here"
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={() => {
|
||||
saveOllamaURL(ollamaURL)
|
||||
}}
|
||||
className="bg-pink-500 text-r mt-4 hover:bg-pink-600 text-white px-4 py-2 rounded-md dark:bg-pink-600 dark:hover:bg-pink-700">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border border-gray-300 dark:border-gray-700 rounded p-4">
|
||||
<h2 className="text-md font-semibold dark:text-white">Prompt</h2>
|
||||
<div className="my-3 flex justify-end">
|
||||
<Radio.Group
|
||||
defaultValue={selectedValue}
|
||||
onChange={(e) => setSelectedValue(e.target.value)}>
|
||||
<Radio.Button value="normal">Normal</Radio.Button>
|
||||
<Radio.Button value="rag">Rag</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
{selectedValue === "normal" && (
|
||||
<div>
|
||||
<span className="text-md font-thin text-gray-500 dark:text-gray-400">
|
||||
System Prompt
|
||||
</span>
|
||||
<textarea
|
||||
className="w-full border border-gray-300 dark:border-gray-700 rounded p-2 dark:bg-black dark:text-white dark:placeholder-gray-400"
|
||||
value={systemPrompt}
|
||||
onChange={(e) => setSystemPrompt(e.target.value)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSystemPromptForNonRag(systemPrompt)
|
||||
}}
|
||||
className="bg-pink-500 text-r mt-4 hover:bg-pink-600 text-white px-4 py-2 rounded-md dark:bg-pink-600 dark:hover:bg-pink-700">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedValue === "rag" && (
|
||||
<div>
|
||||
<div className="mb-3">
|
||||
<span className="text-md font-thin text-gray-500 dark:text-gray-400">
|
||||
System Prompt
|
||||
</span>
|
||||
<textarea
|
||||
className="w-full border border-gray-300 dark:border-gray-700 rounded p-2 dark:bg-black dark:text-white dark:placeholder-gray-400"
|
||||
value={ragPrompt}
|
||||
onChange={(e) => setRagPrompt(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<span className="text-md font-thin text-gray-500 dark:text-gray-400">
|
||||
Question Prompt
|
||||
</span>
|
||||
<textarea
|
||||
className="w-full border border-gray-300 dark:border-gray-700 rounded p-2 dark:bg-black dark:text-white dark:placeholder-gray-400"
|
||||
value={ragQuestionPrompt}
|
||||
onChange={(e) => setRagQuestionPrompt(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={() => {
|
||||
setPromptForRag(ragPrompt, ragQuestionPrompt)
|
||||
}}
|
||||
className="bg-pink-500 hover:bg-pink-600 text-white px-4 py-2 rounded-md dark:bg-pink-600 dark:hover:bg-pink-700">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border border-gray-300 dark:border-gray-700 rounded p-4">
|
||||
<h2 className="text-md mb-4 font-semibold dark:text-white">Theme</h2>
|
||||
{mode === "dark" ? (
|
||||
<button
|
||||
onClick={toggleDarkMode}
|
||||
className="select-none w-full rounded-lg border border-gray-900 py-3 px-6 text-center align-middle font-sans text-xs font-bold uppercase text-gray-900 transition-all hover:opacity-75 focus:ring focus:ring-gray-300 active:opacity-[0.85] disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none dark:border-gray-100 dark:text-white dark:hover:opacity-75 dark:focus:ring-dark dark:active:opacity-75 dark:disabled:pointer-events-none dark:disabled:opacity-50 dark:disabled:shadow-none">
|
||||
Light Mode
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={toggleDarkMode}
|
||||
className="select-none w-full rounded-lg border border-gray-900 py-3 px-6 text-center align-middle font-sans text-xs font-bold uppercase text-gray-900 transition-all hover:opacity-75 focus:ring focus:ring-gray-300 active:opacity-[0.85] disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none dark:border-gray-100 dark:text-white dark:hover:opacity-75 dark:focus:ring-dark dark:active:opacity-75 dark:disabled:pointer-events-none dark:disabled:opacity-50 dark:disabled:shadow-none">
|
||||
Dark Mode
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,12 +1,17 @@
|
||||
import React from "react"
|
||||
import { cleanUrl } from "~libs/clean-url"
|
||||
import { getOllamaURL, systemPromptForNonRag } from "~services/ollama"
|
||||
import {
|
||||
getOllamaURL,
|
||||
promptForRag,
|
||||
systemPromptForNonRag
|
||||
} from "~services/ollama"
|
||||
import { useStoreMessage, type ChatHistory, type Message } from "~store"
|
||||
import { ChatOllama } from "@langchain/community/chat_models/ollama"
|
||||
import {
|
||||
HumanMessage,
|
||||
AIMessage,
|
||||
type MessageContent
|
||||
type MessageContent,
|
||||
SystemMessage
|
||||
} from "@langchain/core/messages"
|
||||
import { getHtmlOfCurrentTab } from "~libs/get-html"
|
||||
import { PageAssistHtmlLoader } from "~loader/html"
|
||||
@ -185,10 +190,8 @@ export const useMessage = () => {
|
||||
vectorstore = await memoryEmbedding(url, html, ollamaEmbedding)
|
||||
}
|
||||
|
||||
const questionPrompt =
|
||||
"Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question. Chat History: {chat_history} Follow Up Input: {question} Standalone question:"
|
||||
|
||||
const systemPrompt = `You are a helpful AI assistant. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say you don't know. DO NOT try to make up an answer. If the question is not related to the context, politely respond that you are tuned to only answer questions that are related to the context. {context} Question: {question} Helpful answer in markdown:`
|
||||
const { ragPrompt: systemPrompt, ragQuestionPrompt: questionPrompt } =
|
||||
await promptForRag()
|
||||
|
||||
const sanitizedQuestion = message.trim().replaceAll("\n", " ")
|
||||
|
||||
@ -247,9 +250,9 @@ export const useMessage = () => {
|
||||
isBot: true,
|
||||
name: selectedModel,
|
||||
message: `Something went wrong. Check out the following logs:
|
||||
\`\`\`
|
||||
${e?.message}
|
||||
\`\`\`
|
||||
~~~
|
||||
${e?.message}
|
||||
~~~
|
||||
`,
|
||||
sources: []
|
||||
}
|
||||
@ -283,7 +286,7 @@ export const useMessage = () => {
|
||||
isBot: true,
|
||||
name: selectedModel,
|
||||
message: "▋",
|
||||
sources: [],
|
||||
sources: []
|
||||
}
|
||||
]
|
||||
|
||||
@ -293,7 +296,9 @@ export const useMessage = () => {
|
||||
try {
|
||||
const prompt = await systemPromptForNonRag()
|
||||
|
||||
let humanMessage = new HumanMessage({
|
||||
message = message.trim().replaceAll("\n", " ")
|
||||
|
||||
let humanMessage = new HumanMessage({
|
||||
content: [
|
||||
{
|
||||
text: message,
|
||||
@ -316,13 +321,23 @@ export const useMessage = () => {
|
||||
})
|
||||
}
|
||||
|
||||
console.log("humanMessage", humanMessage)
|
||||
const applicationChatHistory = generateHistory(history)
|
||||
|
||||
if (prompt) {
|
||||
applicationChatHistory.unshift(
|
||||
new SystemMessage({
|
||||
content: [
|
||||
{
|
||||
text: prompt,
|
||||
type: "text"
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const chunks = await ollama.stream(
|
||||
[
|
||||
...generateHistory(history),
|
||||
humanMessage
|
||||
],
|
||||
[...applicationChatHistory, humanMessage],
|
||||
{
|
||||
signal: abortControllerRef.current.signal
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Route, Routes } from "react-router-dom"
|
||||
import { SidepanelChat } from "./sidepanel-chat"
|
||||
import { SidepanelSettingsHeader } from "~components/Sidepanel/Settings/header"
|
||||
import { useDarkMode } from "~hooks/useDarkmode"
|
||||
import { SidepanelSettings } from "./sidepanel-settings"
|
||||
|
||||
export const Routing = () => <Routes></Routes>
|
||||
|
||||
@ -12,7 +12,7 @@ export const SidepanelRouting = () => {
|
||||
<div className={mode === "dark" ? "dark" : "light"}>
|
||||
<Routes>
|
||||
<Route path="/" element={<SidepanelChat />} />
|
||||
<Route path="/settings" element={<SidepanelSettingsHeader />} />
|
||||
<Route path="/settings" element={<SidepanelSettings />} />
|
||||
</Routes>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { SettingsBody } from "~components/Sidepanel/Settings/body"
|
||||
import { SidepanelSettingsHeader } from "~components/Sidepanel/Settings/header"
|
||||
|
||||
export const SidepanelSettings = () => {
|
||||
@ -6,6 +7,7 @@ export const SidepanelSettings = () => {
|
||||
<div className="sticky top-0 z-10">
|
||||
<SidepanelSettingsHeader />
|
||||
</div>
|
||||
<SettingsBody />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -7,6 +7,11 @@ const storage = new Storage()
|
||||
const DEFAULT_OLLAMA_URL = "http://127.0.0.1:11434"
|
||||
const DEFAULT_ASK_FOR_MODEL_SELECTION_EVERY_TIME = true
|
||||
|
||||
const DEFAULT_RAG_QUESTION_PROMPT =
|
||||
"Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question. Chat History: {chat_history} Follow Up Input: {question} Standalone question:"
|
||||
|
||||
const DEFAUTL_RAG_SYSTEM_PROMPT = `You are a helpful AI assistant. Use the following pieces of context to answer the question at the end. If you don't know the answer, just say you don't know. DO NOT try to make up an answer. If the question is not related to the context, politely respond that you are tuned to only answer questions that are related to the context. {context} Question: {question} Helpful answer in markdown:`
|
||||
|
||||
export const getOllamaURL = async () => {
|
||||
const ollamaURL = await storage.get("ollamaURL")
|
||||
if (!ollamaURL || ollamaURL.length === 0) {
|
||||
@ -83,3 +88,36 @@ export const systemPromptForNonRag = async () => {
|
||||
const prompt = await storage.get("systemPromptForNonRag")
|
||||
return prompt
|
||||
}
|
||||
|
||||
export const promptForRag = async () => {
|
||||
const prompt = await storage.get("systemPromptForRag")
|
||||
const questionPrompt = await storage.get("questionPromptForRag")
|
||||
|
||||
let ragPrompt = prompt
|
||||
let ragQuestionPrompt = questionPrompt
|
||||
|
||||
if (!ragPrompt || ragPrompt.length === 0) {
|
||||
ragPrompt = DEFAUTL_RAG_SYSTEM_PROMPT
|
||||
}
|
||||
|
||||
if (!ragQuestionPrompt || ragQuestionPrompt.length === 0) {
|
||||
ragQuestionPrompt = DEFAULT_RAG_QUESTION_PROMPT
|
||||
}
|
||||
|
||||
return {
|
||||
ragPrompt,
|
||||
ragQuestionPrompt
|
||||
}
|
||||
}
|
||||
|
||||
export const setSystemPromptForNonRag = async (prompt: string) => {
|
||||
await storage.set("systemPromptForNonRag", prompt)
|
||||
}
|
||||
|
||||
export const setPromptForRag = async (
|
||||
prompt: string,
|
||||
questionPrompt: string
|
||||
) => {
|
||||
await storage.set("systemPromptForRag", prompt)
|
||||
await storage.set("questionPromptForRag", questionPrompt)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user