mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-27 20:56:12 +01:00
🪨 fix: Minor AWS Bedrock/Misc. Improvements (#3974)
* refactor(EditMessage): avoid manipulation of native paste handling, leverage react-hook-form for textarea changes * style: apply better theming for MinimalIcon * fix(useVoicesQuery/useCustomConfigSpeechQuery): make sure to only try request once per render * feat: edit message content parts * fix(useCopyToClipboard): handle both assistants and agents content blocks * refactor: remove save & submit and update text content correctly * chore(.env.example/config): exclude unsupported bedrock models * feat: artifacts for aws bedrock * fix: export options for bedrock conversations
This commit is contained in:
parent
341e086d70
commit
1a1e6850a3
23 changed files with 441 additions and 203 deletions
|
|
@ -1,5 +1,7 @@
|
|||
import { memo } from 'react';
|
||||
import { ContentTypes } from 'librechat-data-provider';
|
||||
import type { TMessageContentParts } from 'librechat-data-provider';
|
||||
import EditTextPart from './Parts/EditTextPart';
|
||||
import Part from './Part';
|
||||
|
||||
type ContentPartsProps = {
|
||||
|
|
@ -8,13 +10,54 @@ type ContentPartsProps = {
|
|||
isCreatedByUser: boolean;
|
||||
isLast: boolean;
|
||||
isSubmitting: boolean;
|
||||
edit?: boolean;
|
||||
enterEdit?: (cancel?: boolean) => void | null | undefined;
|
||||
siblingIdx?: number;
|
||||
setSiblingIdx?:
|
||||
| ((value: number) => void | React.Dispatch<React.SetStateAction<number>>)
|
||||
| null
|
||||
| undefined;
|
||||
};
|
||||
|
||||
const ContentParts = memo(
|
||||
({ content, messageId, isCreatedByUser, isLast, isSubmitting }: ContentPartsProps) => {
|
||||
({
|
||||
content,
|
||||
messageId,
|
||||
isCreatedByUser,
|
||||
isLast,
|
||||
isSubmitting,
|
||||
edit,
|
||||
enterEdit,
|
||||
siblingIdx,
|
||||
setSiblingIdx,
|
||||
}: ContentPartsProps) => {
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
if (edit === true && enterEdit && setSiblingIdx) {
|
||||
return (
|
||||
<>
|
||||
{content.map((part, idx) => {
|
||||
if (part?.type !== ContentTypes.TEXT || typeof part.text !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EditTextPart
|
||||
index={idx}
|
||||
text={part.text}
|
||||
messageId={messageId}
|
||||
isSubmitting={isSubmitting}
|
||||
enterEdit={enterEdit}
|
||||
siblingIdx={siblingIdx ?? null}
|
||||
setSiblingIdx={setSiblingIdx}
|
||||
key={`edit-${messageId}-${idx}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{content
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { useRef, useEffect, useCallback } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useUpdateMessageMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TEditProps } from '~/common';
|
||||
import { useChatContext, useAddedChatContext } from '~/Providers';
|
||||
import { TextareaAutosize } from '~/components/ui';
|
||||
import { cn, removeFocusRings } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import Container from './Container';
|
||||
|
|
@ -25,7 +26,6 @@ const EditMessage = ({
|
|||
store.latestMessageFamily(addedIndex),
|
||||
);
|
||||
|
||||
const [editedText, setEditedText] = useState<string>(text ?? '');
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const { conversationId, parentMessageId, messageId } = message;
|
||||
|
|
@ -34,6 +34,15 @@ const EditMessage = ({
|
|||
const updateMessageMutation = useUpdateMessageMutation(conversationId ?? '');
|
||||
const localize = useLocalize();
|
||||
|
||||
const chatDirection = useRecoilValue(store.chatDirection).toLowerCase();
|
||||
const isRTL = chatDirection === 'rtl';
|
||||
|
||||
const { register, handleSubmit, setValue } = useForm({
|
||||
defaultValues: {
|
||||
text: text ?? '',
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const textArea = textAreaRef.current;
|
||||
if (textArea) {
|
||||
|
|
@ -43,11 +52,11 @@ const EditMessage = ({
|
|||
}
|
||||
}, []);
|
||||
|
||||
const resubmitMessage = () => {
|
||||
const resubmitMessage = (data: { text: string }) => {
|
||||
if (message.isCreatedByUser) {
|
||||
ask(
|
||||
{
|
||||
text: editedText,
|
||||
text: data.text,
|
||||
parentMessageId,
|
||||
conversationId,
|
||||
},
|
||||
|
|
@ -67,7 +76,7 @@ const EditMessage = ({
|
|||
ask(
|
||||
{ ...parentMessage },
|
||||
{
|
||||
editedText,
|
||||
editedText: data.text,
|
||||
editedMessageId: messageId,
|
||||
isRegenerate: true,
|
||||
isEdited: true,
|
||||
|
|
@ -80,7 +89,7 @@ const EditMessage = ({
|
|||
enterEdit(true);
|
||||
};
|
||||
|
||||
const updateMessage = () => {
|
||||
const updateMessage = (data: { text: string }) => {
|
||||
const messages = getMessages();
|
||||
if (!messages) {
|
||||
return;
|
||||
|
|
@ -88,24 +97,24 @@ const EditMessage = ({
|
|||
updateMessageMutation.mutate({
|
||||
conversationId: conversationId ?? '',
|
||||
model: conversation?.model ?? 'gpt-3.5-turbo',
|
||||
text: editedText,
|
||||
text: data.text,
|
||||
messageId,
|
||||
});
|
||||
|
||||
if (message.messageId === latestMultiMessage?.messageId) {
|
||||
setLatestMultiMessage({ ...latestMultiMessage, text: editedText });
|
||||
setLatestMultiMessage({ ...latestMultiMessage, text: data.text });
|
||||
}
|
||||
|
||||
const isInMessages = messages?.some((message) => message?.messageId === messageId);
|
||||
const isInMessages = messages.some((message) => message.messageId === messageId);
|
||||
if (!isInMessages) {
|
||||
message.text = editedText;
|
||||
message.text = data.text;
|
||||
} else {
|
||||
setMessages(
|
||||
messages.map((msg) =>
|
||||
msg.messageId === messageId
|
||||
? {
|
||||
...msg,
|
||||
text: editedText,
|
||||
text: data.text,
|
||||
isEdited: true,
|
||||
}
|
||||
: msg,
|
||||
|
|
@ -126,43 +135,33 @@ const EditMessage = ({
|
|||
[enterEdit],
|
||||
);
|
||||
|
||||
const { ref, ...registerProps } = register('text', {
|
||||
required: true,
|
||||
onChange: (e) => {
|
||||
setValue('text', e.target.value, { shouldValidate: true });
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Container message={message}>
|
||||
<div className="bg-token-main-surface-primary relative flex w-full flex-grow flex-col overflow-hidden rounded-2xl border dark:border-gray-600 dark:text-white [&:has(textarea:focus)]:border-gray-300 [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)] dark:[&:has(textarea:focus)]:border-gray-500">
|
||||
<div className="bg-token-main-surface-primary relative flex w-full flex-grow flex-col overflow-hidden rounded-2xl border border-border-medium text-text-primary [&:has(textarea:focus)]:border-border-heavy [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)]">
|
||||
<TextareaAutosize
|
||||
ref={textAreaRef}
|
||||
onChange={(e) => {
|
||||
setEditedText(e.target.value);
|
||||
{...registerProps}
|
||||
ref={(e) => {
|
||||
ref(e);
|
||||
textAreaRef.current = e;
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
data-testid="message-text-editor"
|
||||
className={cn(
|
||||
'markdown prose dark:prose-invert light whitespace-pre-wrap break-words',
|
||||
'pl-3 md:pl-4',
|
||||
'markdown prose dark:prose-invert light whitespace-pre-wrap break-words pl-3 md:pl-4',
|
||||
'm-0 w-full resize-none border-0 bg-transparent py-[10px]',
|
||||
'placeholder-black/50 focus:ring-0 focus-visible:ring-0 dark:bg-transparent dark:placeholder-white/50 md:py-3.5 ',
|
||||
'pr-3 md:pr-4',
|
||||
'max-h-[65vh] md:max-h-[75vh]',
|
||||
'placeholder-text-secondary focus:ring-0 focus-visible:ring-0 md:py-3.5',
|
||||
isRTL ? 'text-right' : 'text-left',
|
||||
'max-h-[65vh] pr-3 md:max-h-[75vh] md:pr-4',
|
||||
removeFocusRings,
|
||||
)}
|
||||
onPaste={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const pastedData = e.clipboardData.getData('text/plain');
|
||||
const textArea = textAreaRef.current;
|
||||
if (!textArea) {
|
||||
return;
|
||||
}
|
||||
const start = textArea.selectionStart;
|
||||
const end = textArea.selectionEnd;
|
||||
const newValue =
|
||||
textArea.value.substring(0, start) + pastedData + textArea.value.substring(end);
|
||||
setEditedText(newValue);
|
||||
}}
|
||||
contentEditable={true}
|
||||
value={editedText}
|
||||
suppressContentEditableWarning={true}
|
||||
dir="auto"
|
||||
dir={isRTL ? 'rtl' : 'ltr'}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex w-full justify-center text-center">
|
||||
|
|
@ -171,14 +170,14 @@ const EditMessage = ({
|
|||
disabled={
|
||||
isSubmitting || (endpoint === EModelEndpoint.google && !message.isCreatedByUser)
|
||||
}
|
||||
onClick={resubmitMessage}
|
||||
onClick={handleSubmit(resubmitMessage)}
|
||||
>
|
||||
{localize('com_ui_save_submit')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={updateMessage}
|
||||
onClick={handleSubmit(updateMessage)}
|
||||
>
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,193 @@
|
|||
import { useForm } from 'react-hook-form';
|
||||
import { ContentTypes } from 'librechat-data-provider';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useUpdateMessageContentMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TEditProps } from '~/common';
|
||||
import Container from '~/components/Chat/Messages/Content/Container';
|
||||
import { useChatContext, useAddedChatContext } from '~/Providers';
|
||||
import { TextareaAutosize } from '~/components/ui';
|
||||
import { cn, removeFocusRings } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
const EditTextPart = ({
|
||||
text,
|
||||
index,
|
||||
messageId,
|
||||
isSubmitting,
|
||||
enterEdit,
|
||||
}: Omit<TEditProps, 'message' | 'ask'> & {
|
||||
index: number;
|
||||
messageId: string;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const { addedIndex } = useAddedChatContext();
|
||||
const { getMessages, setMessages, conversation } = useChatContext();
|
||||
const [latestMultiMessage, setLatestMultiMessage] = useRecoilState(
|
||||
store.latestMessageFamily(addedIndex),
|
||||
);
|
||||
|
||||
const { conversationId = '' } = conversation ?? {};
|
||||
const message = useMemo(
|
||||
() => getMessages()?.find((msg) => msg.messageId === messageId),
|
||||
[getMessages, messageId],
|
||||
);
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const updateMessageContentMutation = useUpdateMessageContentMutation(conversationId ?? '');
|
||||
|
||||
const chatDirection = useRecoilValue(store.chatDirection).toLowerCase();
|
||||
const isRTL = chatDirection === 'rtl';
|
||||
|
||||
const { register, handleSubmit, setValue } = useForm({
|
||||
defaultValues: {
|
||||
text: text ?? '',
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const textArea = textAreaRef.current;
|
||||
if (textArea) {
|
||||
const length = textArea.value.length;
|
||||
textArea.focus();
|
||||
textArea.setSelectionRange(length, length);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/*
|
||||
const resubmitMessage = () => {
|
||||
showToast({
|
||||
status: 'warning',
|
||||
message: localize('com_warning_resubmit_unsupported'),
|
||||
});
|
||||
|
||||
// const resubmitMessage = (data: { text: string }) => {
|
||||
// Not supported by AWS Bedrock
|
||||
const messages = getMessages();
|
||||
const parentMessage = messages?.find((msg) => msg.messageId === message?.parentMessageId);
|
||||
|
||||
if (!parentMessage) {
|
||||
return;
|
||||
}
|
||||
ask(
|
||||
{ ...parentMessage },
|
||||
{
|
||||
editedText: data.text,
|
||||
editedMessageId: messageId,
|
||||
isRegenerate: true,
|
||||
isEdited: true,
|
||||
},
|
||||
);
|
||||
|
||||
setSiblingIdx((siblingIdx ?? 0) - 1);
|
||||
enterEdit(true);
|
||||
};
|
||||
*/
|
||||
|
||||
const updateMessage = (data: { text: string }) => {
|
||||
const messages = getMessages();
|
||||
if (!messages) {
|
||||
return;
|
||||
}
|
||||
updateMessageContentMutation.mutate({
|
||||
index,
|
||||
conversationId: conversationId ?? '',
|
||||
text: data.text,
|
||||
messageId,
|
||||
});
|
||||
|
||||
if (messageId === latestMultiMessage?.messageId) {
|
||||
setLatestMultiMessage({ ...latestMultiMessage, text: data.text });
|
||||
}
|
||||
|
||||
const isInMessages = messages.some((msg) => msg.messageId === messageId);
|
||||
if (!isInMessages) {
|
||||
return enterEdit(true);
|
||||
}
|
||||
|
||||
const updatedContent = message?.content?.map((part, idx) => {
|
||||
if (part.type === ContentTypes.TEXT && idx === index) {
|
||||
return { ...part, text: data.text };
|
||||
}
|
||||
return part;
|
||||
});
|
||||
|
||||
setMessages(
|
||||
messages.map((msg) =>
|
||||
msg.messageId === messageId
|
||||
? {
|
||||
...msg,
|
||||
content: updatedContent,
|
||||
isEdited: true,
|
||||
}
|
||||
: msg,
|
||||
),
|
||||
);
|
||||
|
||||
enterEdit(true);
|
||||
};
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
enterEdit(true);
|
||||
}
|
||||
},
|
||||
[enterEdit],
|
||||
);
|
||||
|
||||
const { ref, ...registerProps } = register('text', {
|
||||
required: true,
|
||||
onChange: (e) => {
|
||||
setValue('text', e.target.value, { shouldValidate: true });
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Container message={message}>
|
||||
<div className="bg-token-main-surface-primary relative flex w-full flex-grow flex-col overflow-hidden rounded-2xl border border-border-medium text-text-primary [&:has(textarea:focus)]:border-border-heavy [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)]">
|
||||
<TextareaAutosize
|
||||
{...registerProps}
|
||||
ref={(e) => {
|
||||
ref(e);
|
||||
textAreaRef.current = e;
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
data-testid="message-text-editor"
|
||||
className={cn(
|
||||
'markdown prose dark:prose-invert light whitespace-pre-wrap break-words pl-3 md:pl-4',
|
||||
'm-0 w-full resize-none border-0 bg-transparent py-[10px]',
|
||||
'placeholder-text-secondary focus:ring-0 focus-visible:ring-0 md:py-3.5',
|
||||
isRTL ? 'text-right' : 'text-left',
|
||||
'max-h-[65vh] pr-3 md:max-h-[75vh] md:pr-4',
|
||||
removeFocusRings,
|
||||
)}
|
||||
dir={isRTL ? 'rtl' : 'ltr'}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex w-full justify-center text-center">
|
||||
{/* <button
|
||||
className="btn btn-primary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={handleSubmit(resubmitMessage)}
|
||||
>
|
||||
{localize('com_ui_save_submit')}
|
||||
</button> */}
|
||||
<button
|
||||
className="btn btn-secondary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={handleSubmit(updateMessage)}
|
||||
>
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
<button className="btn btn-neutral relative" onClick={() => enterEdit(true)}>
|
||||
{localize('com_ui_cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditTextPart;
|
||||
|
|
@ -83,13 +83,13 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
|
|||
height: size,
|
||||
}}
|
||||
className={cn(
|
||||
'relative flex items-center justify-center rounded-sm text-black dark:text-white',
|
||||
'relative flex items-center justify-center rounded-sm text-text-secondary',
|
||||
props.className ?? '',
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
{error === true && (
|
||||
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-black dark:text-white">
|
||||
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-text-secondary">
|
||||
!
|
||||
</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,117 +0,0 @@
|
|||
import { useRef } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useUpdateMessageMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TEditProps } from '~/common';
|
||||
import store from '~/store';
|
||||
import Container from './Container';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const EditMessage = ({
|
||||
text,
|
||||
message,
|
||||
isSubmitting,
|
||||
ask,
|
||||
enterEdit,
|
||||
siblingIdx,
|
||||
setSiblingIdx,
|
||||
}: TEditProps) => {
|
||||
const [messages, setMessages] = useRecoilState(store.messages);
|
||||
const conversation = useRecoilValue(store.conversation);
|
||||
const textEditor = useRef<HTMLDivElement | null>(null);
|
||||
const { conversationId, parentMessageId, messageId } = message;
|
||||
const updateMessageMutation = useUpdateMessageMutation(conversationId ?? '');
|
||||
const localize = useLocalize();
|
||||
|
||||
const resubmitMessage = () => {
|
||||
const text = textEditor?.current?.innerText ?? '';
|
||||
if (message.isCreatedByUser) {
|
||||
ask({
|
||||
text,
|
||||
parentMessageId,
|
||||
conversationId,
|
||||
});
|
||||
|
||||
setSiblingIdx((siblingIdx ?? 0) - 1);
|
||||
} else {
|
||||
const parentMessage = messages?.find((msg) => msg.messageId === parentMessageId);
|
||||
|
||||
if (!parentMessage) {
|
||||
return;
|
||||
}
|
||||
ask(
|
||||
{ ...parentMessage },
|
||||
{
|
||||
editedText: text,
|
||||
editedMessageId: messageId,
|
||||
isRegenerate: true,
|
||||
isEdited: true,
|
||||
},
|
||||
);
|
||||
|
||||
setSiblingIdx((siblingIdx ?? 0) - 1);
|
||||
}
|
||||
|
||||
enterEdit(true);
|
||||
};
|
||||
|
||||
const updateMessage = () => {
|
||||
if (!messages) {
|
||||
return;
|
||||
}
|
||||
const text = textEditor?.current?.innerText ?? '';
|
||||
updateMessageMutation.mutate({
|
||||
conversationId: conversationId ?? '',
|
||||
model: conversation?.model ?? 'gpt-3.5-turbo',
|
||||
messageId,
|
||||
text,
|
||||
});
|
||||
setMessages(() =>
|
||||
messages.map((msg) =>
|
||||
msg.messageId === messageId
|
||||
? {
|
||||
...msg,
|
||||
text,
|
||||
isEdited: true,
|
||||
}
|
||||
: msg,
|
||||
),
|
||||
);
|
||||
enterEdit(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div
|
||||
data-testid="message-text-editor"
|
||||
className="markdown prose dark:prose-invert light w-full whitespace-pre-wrap break-words border-none focus:outline-none"
|
||||
contentEditable={true}
|
||||
ref={textEditor}
|
||||
suppressContentEditableWarning={true}
|
||||
dir="auto"
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
<div className="mt-2 flex w-full justify-center text-center">
|
||||
<button
|
||||
className="btn btn-primary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={resubmitMessage}
|
||||
>
|
||||
{localize('com_ui_save_submit')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={updateMessage}
|
||||
>
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
<button className="btn btn-neutral relative" onClick={() => enterEdit(true)}>
|
||||
{localize('com_ui_cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditMessage;
|
||||
|
|
@ -134,6 +134,10 @@ const ContentRender = memo(
|
|||
isCreatedByUser={msg.isCreatedByUser}
|
||||
isLast={isLast}
|
||||
isSubmitting={isSubmitting}
|
||||
edit={edit}
|
||||
enterEdit={enterEdit}
|
||||
siblingIdx={siblingIdx}
|
||||
setSiblingIdx={setSiblingIdx}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default function ExportModal({
|
|||
];
|
||||
|
||||
useEffect(() => {
|
||||
setFileName(filenamify(String(conversation?.title || 'file')));
|
||||
setFileName(filenamify(String(conversation?.title ?? 'file')));
|
||||
setType('screenshot');
|
||||
setIncludeOptions(true);
|
||||
setExportBranches(false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue