chore: Refactor Sidepanel Chat body and form components

This commit is contained in:
n4ze3m 2024-05-18 16:25:06 +05:30
parent d28525ad02
commit 160927a5a6
3 changed files with 163 additions and 61 deletions

View File

@ -5,7 +5,8 @@ import { EmptySidePanel } from "../Chat/empty"
import { useWebUI } from "@/store/webui" import { useWebUI } from "@/store/webui"
export const SidePanelBody = () => { export const SidePanelBody = () => {
const { messages, streaming } = useMessage() const { messages, streaming, regenerateLastMessage, editMessage } =
useMessage()
const divRef = React.useRef<HTMLDivElement>(null) const divRef = React.useRef<HTMLDivElement>(null)
const { ttsEnabled } = useWebUI() const { ttsEnabled } = useWebUI()
React.useEffect(() => { React.useEffect(() => {
@ -18,7 +19,6 @@ export const SidePanelBody = () => {
{messages.length === 0 && <EmptySidePanel />} {messages.length === 0 && <EmptySidePanel />}
{messages.map((message, index) => ( {messages.map((message, index) => (
<PlaygroundMessage <PlaygroundMessage
onEditFormSubmit={(value) => {}}
key={index} key={index}
isBot={message.isBot} isBot={message.isBot}
message={message.message} message={message.message}
@ -26,15 +26,19 @@ export const SidePanelBody = () => {
images={message.images || []} images={message.images || []}
currentMessageIndex={index} currentMessageIndex={index}
totalMessages={messages.length} totalMessages={messages.length}
onRengerate={() => {}} onRengerate={regenerateLastMessage}
onEditFormSubmit={(value) => {
editMessage(index, value, !message.isBot)
}}
isProcessing={streaming} isProcessing={streaming}
hideEditAndRegenerate
isTTSEnabled={ttsEnabled} isTTSEnabled={ttsEnabled}
/> />
))} ))}
{ {import.meta.env.BROWSER === "chrome" ? (
import.meta.env.BROWSER === "chrome" ? <div className="w-full h-32 md:h-48 flex-shrink-0"></div> : <div className="w-full h-48 flex-shrink-0"></div> <div className="w-full h-32 md:h-48 flex-shrink-0"></div>
} ) : (
<div className="w-full h-48 flex-shrink-0"></div>
)}
<div ref={divRef} /> <div ref={divRef} />
</div> </div>
) )

View File

@ -23,11 +23,6 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
const [typing, setTyping] = React.useState<boolean>(false) const [typing, setTyping] = React.useState<boolean>(false)
const { t } = useTranslation(["playground", "common"]) const { t } = useTranslation(["playground", "common"])
const textAreaFocus = () => {
if (textareaRef.current) {
textareaRef.current.focus()
}
}
const form = useForm({ const form = useForm({
initialValues: { initialValues: {
message: "", message: "",
@ -48,39 +43,11 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
} }
} }
} }
const textAreaFocus = () => {
React.useEffect(() => { if (textareaRef.current) {
if (dropedFile) { textareaRef.current.focus()
onInputChange(dropedFile)
} }
}, [dropedFile]) }
useDynamicTextareaSize(textareaRef, form.values.message, 120)
const {
onSubmit,
selectedModel,
chatMode,
speechToTextLanguage,
stopStreamingRequest
} = useMessage()
const { isListening, start, stop, transcript } = useSpeechRecognition()
React.useEffect(() => {
if (isListening) {
form.setFieldValue("message", transcript)
}
}, [transcript])
const { mutateAsync: sendMessage, isPending: isSending } = useMutation({
mutationFn: onSubmit,
onSuccess: () => {
textAreaFocus()
},
onError: (error) => {
textAreaFocus()
}
})
const handleKeyDown = (e: React.KeyboardEvent) => { const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Process" || e.key === "229") return if (e.key === "Process" || e.key === "229") return
if ( if (
@ -116,6 +83,39 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
} }
} }
const {
onSubmit,
selectedModel,
chatMode,
speechToTextLanguage,
stopStreamingRequest,
streaming
} = useMessage()
const { isListening, start, stop, transcript } = useSpeechRecognition()
React.useEffect(() => {
if (dropedFile) {
onInputChange(dropedFile)
}
}, [dropedFile])
useDynamicTextareaSize(textareaRef, form.values.message, 120)
React.useEffect(() => {
if (isListening) {
form.setFieldValue("message", transcript)
}
}, [transcript])
const { mutateAsync: sendMessage, isPending: isSending } = useMutation({
mutationFn: onSubmit,
onSuccess: () => {
textAreaFocus()
},
onError: (error) => {
textAreaFocus()
}
})
return ( return (
<div className="px-3 pt-3 md:px-6 md:pt-6 bg-gray-50 dark:bg-[#262626] border rounded-t-xl border-black/10 dark:border-gray-600"> <div className="px-3 pt-3 md:px-6 md:pt-6 bg-gray-50 dark:bg-[#262626] border rounded-t-xl border-black/10 dark:border-gray-600">
<div <div
@ -224,7 +224,7 @@ export const SidepanelForm = ({ dropedFile }: Props) => {
<ImageIcon className="h-5 w-5" /> <ImageIcon className="h-5 w-5" />
</button> </button>
</Tooltip> </Tooltip>
{!isSending ? ( {!streaming ? (
<Dropdown.Button <Dropdown.Button
htmlType="submit" htmlType="submit"
disabled={isSending} disabled={isSending}

View File

@ -11,11 +11,15 @@ import { useStoreMessage } from "~/store"
import { ChatOllama } from "@langchain/community/chat_models/ollama" import { ChatOllama } from "@langchain/community/chat_models/ollama"
import { HumanMessage, SystemMessage } from "@langchain/core/messages" import { HumanMessage, SystemMessage } from "@langchain/core/messages"
import { getDataFromCurrentTab } from "~/libs/get-html" import { getDataFromCurrentTab } from "~/libs/get-html"
import { OllamaEmbeddings } from "@langchain/community/embeddings/ollama"
import { MemoryVectorStore } from "langchain/vectorstores/memory" import { MemoryVectorStore } from "langchain/vectorstores/memory"
import { memoryEmbedding } from "@/utils/memory-embeddings" import { memoryEmbedding } from "@/utils/memory-embeddings"
import { ChatHistory } from "@/store/option" import { ChatHistory } from "@/store/option"
import { generateID } from "@/db" import {
deleteChatForEdit,
generateID,
removeMessageUsingHistoryId,
updateMessageByIndex
} from "@/db"
import { saveMessageOnError, saveMessageOnSuccess } from "./chat-helper" import { saveMessageOnError, saveMessageOnSuccess } from "./chat-helper"
import { notification } from "antd" import { notification } from "antd"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
@ -139,8 +143,20 @@ export const useMessage = () => {
setCurrentURL(url) setCurrentURL(url)
isAlreadyExistEmbedding = keepTrackOfEmbedding[currentURL] isAlreadyExistEmbedding = keepTrackOfEmbedding[currentURL]
} else { } else {
isAlreadyExistEmbedding = keepTrackOfEmbedding[currentURL] const { content: html, url, type, pdf } = await getDataFromCurrentTab()
embedURL = currentURL if (currentURL !== url) {
embedHTML = html
embedURL = url
embedType = type
embedPDF = pdf
setCurrentURL(url)
} else {
embedHTML = html
embedURL = currentURL
embedType = type
embedPDF = pdf
}
isAlreadyExistEmbedding = keepTrackOfEmbedding[url]
} }
setMessages(newMessage) setMessages(newMessage)
@ -157,6 +173,7 @@ export const useMessage = () => {
try { try {
if (isAlreadyExistEmbedding) { if (isAlreadyExistEmbedding) {
vectorstore = isAlreadyExistEmbedding vectorstore = isAlreadyExistEmbedding
console.log("Embedding already exist")
} else { } else {
vectorstore = await memoryEmbedding({ vectorstore = await memoryEmbedding({
html: embedHTML, html: embedHTML,
@ -168,6 +185,8 @@ export const useMessage = () => {
type: embedType, type: embedType,
url: embedURL url: embedURL
}) })
console.log("Embedding created")
} }
let query = message let query = message
const { ragPrompt: systemPrompt, ragQuestionPrompt: questionPrompt } = const { ragPrompt: systemPrompt, ragQuestionPrompt: questionPrompt } =
@ -202,7 +221,7 @@ export const useMessage = () => {
url: "" url: ""
} }
}) })
// message = message.trim().replaceAll("\n", " ") // message = message.trim().replaceAll("\n", " ")
let humanMessage = new HumanMessage({ let humanMessage = new HumanMessage({
content: [ content: [
@ -478,8 +497,6 @@ export const useMessage = () => {
setIsProcessing(false) setIsProcessing(false)
setStreaming(false) setStreaming(false)
setIsProcessing(false)
setStreaming(false)
} catch (e) { } catch (e) {
const errorSave = await saveMessageOnError({ const errorSave = await saveMessageOnError({
e, e,
@ -509,17 +526,38 @@ export const useMessage = () => {
const onSubmit = async ({ const onSubmit = async ({
message, message,
image image,
isRegenerate,
controller,
memory,
messages: chatHistory
}: { }: {
message: string message: string
image: string image: string
isRegenerate?: boolean
messages?: Message[]
memory?: ChatHistory
controller?: AbortController
}) => { }) => {
const newController = new AbortController() let signal: AbortSignal
let signal = newController.signal if (!controller) {
setAbortController(newController) const newController = new AbortController()
signal = newController.signal
setAbortController(newController)
} else {
setAbortController(controller)
signal = controller.signal
}
if (chatMode === "normal") { if (chatMode === "normal") {
await normalChatMode(message, image, false, messages, history, signal) await normalChatMode(
message,
image,
isRegenerate,
chatHistory || messages,
memory || history,
signal
)
} else { } else {
const newEmbeddingController = new AbortController() const newEmbeddingController = new AbortController()
let embeddingSignal = newEmbeddingController.signal let embeddingSignal = newEmbeddingController.signal
@ -527,9 +565,9 @@ export const useMessage = () => {
await chatWithWebsiteMode( await chatWithWebsiteMode(
message, message,
image, image,
false, isRegenerate,
messages, chatHistory || messages,
history, memory || history,
signal, signal,
embeddingSignal embeddingSignal
) )
@ -548,9 +586,68 @@ export const useMessage = () => {
setAbortController(null) setAbortController(null)
} }
} }
const editMessage = async (
index: number,
message: string,
isHuman: boolean
) => {
let newMessages = messages
let newHistory = history
if (isHuman) {
const currentHumanMessage = newMessages[index]
newMessages[index].message = message
const previousMessages = newMessages.slice(0, index + 1)
setMessages(previousMessages)
const previousHistory = newHistory.slice(0, index)
setHistory(previousHistory)
await updateMessageByIndex(historyId, index, message)
await deleteChatForEdit(historyId, index)
const abortController = new AbortController()
await onSubmit({
message: message,
image: currentHumanMessage.images[0] || "",
isRegenerate: true,
messages: previousMessages,
memory: previousHistory,
controller: abortController
})
} else {
newMessages[index].message = message
setMessages(newMessages)
newHistory[index].content = message
setHistory(newHistory)
await updateMessageByIndex(historyId, index, message)
}
}
const regenerateLastMessage = async () => {
if (history.length > 0) {
const lastMessage = history[history.length - 2]
let newHistory = history.slice(0, -2)
let mewMessages = messages
mewMessages.pop()
setHistory(newHistory)
setMessages(mewMessages)
await removeMessageUsingHistoryId(historyId)
if (lastMessage.role === "user") {
const newController = new AbortController()
await onSubmit({
message: lastMessage.content,
image: lastMessage.image || "",
isRegenerate: true,
memory: newHistory,
controller: newController
})
}
}
}
return { return {
messages, messages,
setMessages, setMessages,
editMessage,
onSubmit, onSubmit,
setStreaming, setStreaming,
streaming, streaming,
@ -569,6 +666,7 @@ export const useMessage = () => {
setChatMode, setChatMode,
isEmbedding, isEmbedding,
speechToTextLanguage, speechToTextLanguage,
setSpeechToTextLanguage setSpeechToTextLanguage,
regenerateLastMessage
} }
} }