feat: add save and send functionality for user message

Adds a "Save" button to the edit message form in Playground, allowing users to save changes without immediately submitting them. This also introduces a new `isSend` flag to the `onEditFormSubmit` prop, enabling developers to control whether a message should be sent immediately or saved for later submission. This enhances flexibility and user control during the message editing process.
This commit is contained in:
n4ze3m 2024-11-03 23:38:41 +05:30
parent 31ca246407
commit 65ba2ff898
6 changed files with 53 additions and 44 deletions

View File

@ -8,20 +8,8 @@ import rehypeKatex from "rehype-katex"
import "property-information" import "property-information"
import React from "react" import React from "react"
import { CodeBlock } from "./CodeBlock" import { CodeBlock } from "./CodeBlock"
export const preprocessLaTeX = (content: string) => { import { preprocessLaTeX } from "@/utils/latex"
// Replace block-level LaTeX delimiters \[ \] with $$ $$
const blockProcessedContent = content.replace(
/\\\[(.*?)\\\]/gs,
(_, equation) => `$$${equation}$$`
)
// Replace inline LaTeX delimiters \( \) with $ $
const inlineProcessedContent = blockProcessedContent.replace(
/\\\((.*?)\\\)/gs,
(_, equation) => `$${equation}$`
)
return inlineProcessedContent
}
function Markdown({ function Markdown({
message, message,
className = "prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark" className = "prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 dark:prose-dark"

View File

@ -5,7 +5,7 @@ import useDynamicTextareaSize from "~/hooks/useDynamicTextareaSize"
type Props = { type Props = {
value: string value: string
onSumbit: (value: string) => void onSumbit: (value: string, isSend: boolean) => void
onClose: () => void onClose: () => void
isBot: boolean isBot: boolean
} }
@ -31,7 +31,7 @@ export const EditMessageForm = (props: Props) => {
onSubmit={form.onSubmit((data) => { onSubmit={form.onSubmit((data) => {
if (isComposing) return if (isComposing) return
props.onClose() props.onClose()
props.onSumbit(data.message) props.onSumbit(data.message, true)
})} })}
className="flex flex-col gap-2"> className="flex flex-col gap-2">
<textarea <textarea
@ -46,19 +46,39 @@ export const EditMessageForm = (props: Props) => {
ref={textareaRef} ref={textareaRef}
className="w-full bg-transparent focus-within:outline-none focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100" className="w-full bg-transparent focus-within:outline-none focus:ring-0 focus-visible:ring-0 ring-0 dark:ring-0 border-0 dark:text-gray-100"
/> />
<div className="flex justify-center space-x-2 mt-2"> <div className="flex flex-wrap gap-2 mt-2">
<button <div
aria-label={t("save")} className={`w-full flex ${
className="bg-black px-2.5 py-2 rounded-lg text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-900"> !props.isBot ? "justify-between" : "justify-end"
{props.isBot ? t("save") : t("saveAndSubmit")} }`}>
</button> {!props.isBot && (
<button <button
onClick={props.onClose} type="button"
aria-label={t("cancel")} onClick={() => {
className="border dark:border-gray-600 px-2.5 py-2 rounded-lg text-gray-700 dark:text-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900"> props.onSumbit(form.values.message, false)
{t("cancel")} props.onClose()
</button> }}
</div> aria-label={t("save")}
className="border border-gray-600 px-2 py-1.5 rounded-lg text-gray-700 dark:text-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900 text-sm">
{t("save")}
</button>
)}
<div className="flex space-x-2">
<button
aria-label={t("save")}
className="bg-black px-2 py-1.5 rounded-lg text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-900 text-sm">
{props.isBot ? t("save") : t("saveAndSubmit")}
</button>
<button
onClick={props.onClose}
aria-label={t("cancel")}
className="border dark:border-gray-600 px-2 py-1.5 rounded-lg text-gray-700 dark:text-gray-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900 text-sm">
{t("cancel")}
</button>
</div>
</div>
</div>{" "}
</form> </form>
) )
} }

View File

@ -29,7 +29,7 @@ type Props = {
currentMessageIndex: number currentMessageIndex: number
totalMessages: number totalMessages: number
onRengerate: () => void onRengerate: () => void
onEditFormSubmit: (value: string) => void onEditFormSubmit: (value: string, isSend: boolean) => void
isProcessing: boolean isProcessing: boolean
webSearch?: {} webSearch?: {}
isSearchingInternet?: boolean isSearchingInternet?: boolean

View File

@ -46,8 +46,8 @@ export const PlaygroundChat = () => {
isProcessing={streaming} isProcessing={streaming}
isSearchingInternet={isSearchingInternet} isSearchingInternet={isSearchingInternet}
sources={message.sources} sources={message.sources}
onEditFormSubmit={(value) => { onEditFormSubmit={(value, isSend) => {
editMessage(index, value, !message.isBot) editMessage(index, value, !message.isBot, isSend)
}} }}
onSourceClick={(data) => { onSourceClick={(data) => {
setSource(data) setSource(data)

View File

@ -86,11 +86,10 @@ export const TTSModeSettings = ({ hideBorder }: { hideBorder?: boolean }) => {
placeholder={t("generalSettings.tts.ttsVoice.placeholder")} placeholder={t("generalSettings.tts.ttsVoice.placeholder")}
className="w-full mt-4 sm:mt-0 sm:w-[200px]" className="w-full mt-4 sm:mt-0 sm:w-[200px]"
options={data?.browserTTSVoices?.map( options={data?.browserTTSVoices?.map(
(voice) => (voice) => ({
({ label: `${voice.voiceName} - ${voice.lang}`.trim(),
label: `${voice.voiceName} - ${voice.lang}`.trim(), value: voice.voiceName
value: voice.voiceName })
}) || []
)} )}
{...form.getInputProps("voice")} {...form.getInputProps("voice")}
/> />

View File

@ -910,12 +910,14 @@ export const useMessageOption = () => {
const editMessage = async ( const editMessage = async (
index: number, index: number,
message: string, message: string,
isHuman: boolean isHuman: boolean,
isSend: boolean
) => { ) => {
let newMessages = messages let newMessages = messages
let newHistory = history let newHistory = history
if (isHuman) { // if human message and send then only trigger the submit
if (isHuman && isSend) {
const isOk = validateBeforeSubmit() const isOk = validateBeforeSubmit()
if (!isOk) { if (!isOk) {
@ -939,13 +941,13 @@ export const useMessageOption = () => {
memory: previousHistory, memory: previousHistory,
controller: abortController controller: abortController
}) })
} else { return
newMessages[index].message = message
setMessages(newMessages)
newHistory[index].content = message
setHistory(newHistory)
await updateMessageByIndex(historyId, index, message)
} }
newMessages[index].message = message
setMessages(newMessages)
newHistory[index].content = message
setHistory(newHistory)
await updateMessageByIndex(historyId, index, message)
} }
return { return {