From 8c404ae056da83fd6530fe2bb954a8eccf4fe360 Mon Sep 17 00:00:00 2001 From: Yuichi Oneda Date: Thu, 6 Feb 2025 08:11:47 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=AC=20feat:=20Temporary=20Chats=20(#54?= =?UTF-8?q?93)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add expiredAt property to Conversation and Message models Added `expiredAt` property to both Conversation and Message schemas. Configured `expireAfterSeconds` index in MongoDB to automatically delete documents after a specified period. * feat(data-provider): add isTemporary and expiredAt properties to support temporary chats Added `isTemporary` property to TPayload and TSubmission for API calls for temporary chat. Additionally, added `expiredAt` property to `tConversationSchema` to determine if a chat is temporary. * feat: implement isTemporary state management Add Recoil state for tracking temporary conversations, update event handlers to respect temporary chat status * feat: add configuration to interfaceconfig to hide the temporary chat switch * feat: add Temporary Chat UI with switch and modify related behaviors - Added a Temporary Chat switch button at the end of dropdown lists in each model. - Updated the form background color to black when Temporary Chat is enabled. - Modified Navigation to exclude Temporary Chats from the chat list. * fix: exclude Temporary Chats from search results Updated the getConvosQueried query to ensure that Temporary Chats are not included in the search results. * fix: hide bookmark button for Temporary Chats Updated the UI to ensure that the bookmark button is not displayed when a chat is as Temporary Chat. * chore: update isTemporary state management in ChatRoute * chore: fix to pass the tests --- api/models/Conversation.js | 12 ++++ api/models/Message.js | 9 +++ api/models/schema/convoSchema.js | 5 ++ api/models/schema/messageSchema.js | 5 +- api/server/services/start/interface.js | 1 + .../components/Chat/Input/AudioRecorder.tsx | 8 ++- client/src/components/Chat/Input/ChatForm.tsx | 12 +++- .../components/Chat/Menus/BookmarkMenu.tsx | 5 ++ .../Input/ModelSelect/Anthropic.tsx | 4 +- .../components/Input/ModelSelect/ChatGPT.tsx | 4 +- .../components/Input/ModelSelect/Google.tsx | 4 +- .../components/Input/ModelSelect/OpenAI.tsx | 4 +- .../Input/ModelSelect/TemporaryChat.tsx | 61 +++++++++++++++++++ .../src/components/ui/SelectDropDownPop.tsx | 6 +- client/src/hooks/Chat/useChatFunctions.ts | 2 + client/src/hooks/SSE/useEventHandlers.ts | 13 +++- client/src/hooks/useNewConvo.ts | 4 ++ client/src/routes/ChatRoute.tsx | 17 ++++++ client/src/store/index.ts | 2 + client/src/store/temporary.ts | 10 +++ packages/data-provider/src/config.ts | 2 + packages/data-provider/src/createPayload.ts | 4 +- packages/data-provider/src/schemas.ts | 2 + packages/data-provider/src/types.ts | 2 + 24 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 client/src/components/Input/ModelSelect/TemporaryChat.tsx create mode 100644 client/src/store/temporary.ts diff --git a/api/models/Conversation.js b/api/models/Conversation.js index 8231f4548..d6365e99c 100644 --- a/api/models/Conversation.js +++ b/api/models/Conversation.js @@ -96,6 +96,14 @@ module.exports = { update.conversationId = newConversationId; } + if (req.body.isTemporary) { + const expiredAt = new Date(); + expiredAt.setDate(expiredAt.getDate() + 30); + update.expiredAt = expiredAt; + } else { + update.expiredAt = null; + } + /** Note: the resulting Model object is necessary for Meilisearch operations */ const conversation = await Conversation.findOneAndUpdate( { conversationId, user: req.user.id }, @@ -143,6 +151,9 @@ module.exports = { if (Array.isArray(tags) && tags.length > 0) { query.tags = { $in: tags }; } + + query.$and = [{ $or: [{ expiredAt: null }, { expiredAt: { $exists: false } }] }]; + try { const totalConvos = (await Conversation.countDocuments(query)) || 1; const totalPages = Math.ceil(totalConvos / pageSize); @@ -172,6 +183,7 @@ module.exports = { Conversation.findOne({ user, conversationId: convo.conversationId, + $or: [{ expiredAt: { $exists: false } }, { expiredAt: null }], }).lean(), ), ); diff --git a/api/models/Message.js b/api/models/Message.js index 22e1fa655..e651b20ad 100644 --- a/api/models/Message.js +++ b/api/models/Message.js @@ -52,6 +52,15 @@ async function saveMessage(req, params, metadata) { user: req.user.id, messageId: params.newMessageId || params.messageId, }; + + if (req?.body?.isTemporary) { + const expiredAt = new Date(); + expiredAt.setDate(expiredAt.getDate() + 30); + update.expiredAt = expiredAt; + } else { + update.expiredAt = null; + } + const message = await Message.findOneAndUpdate( { messageId: params.messageId, user: req.user.id }, update, diff --git a/api/models/schema/convoSchema.js b/api/models/schema/convoSchema.js index b910db7d2..7d8beed6a 100644 --- a/api/models/schema/convoSchema.js +++ b/api/models/schema/convoSchema.js @@ -37,6 +37,9 @@ const convoSchema = mongoose.Schema( files: { type: [String], }, + expiredAt: { + type: Date, + }, }, { timestamps: true }, ); @@ -50,6 +53,8 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) { }); } +// Create TTL index +convoSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 }); convoSchema.index({ createdAt: 1, updatedAt: 1 }); convoSchema.index({ conversationId: 1, user: 1 }, { unique: true }); diff --git a/api/models/schema/messageSchema.js b/api/models/schema/messageSchema.js index 000f83e8c..be7115529 100644 --- a/api/models/schema/messageSchema.js +++ b/api/models/schema/messageSchema.js @@ -134,6 +134,9 @@ const messageSchema = mongoose.Schema( default: undefined, }, */ + expiredAt: { + type: Date, + }, }, { timestamps: true }, ); @@ -146,7 +149,7 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) { primaryKey: 'messageId', }); } - +messageSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 }); messageSchema.index({ createdAt: 1 }); messageSchema.index({ messageId: 1, user: 1 }, { unique: true }); diff --git a/api/server/services/start/interface.js b/api/server/services/start/interface.js index 10db2fd3a..98bcb6858 100644 --- a/api/server/services/start/interface.js +++ b/api/server/services/start/interface.js @@ -33,6 +33,7 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol prompts: interfaceConfig?.prompts ?? defaults.prompts, multiConvo: interfaceConfig?.multiConvo ?? defaults.multiConvo, agents: interfaceConfig?.agents ?? defaults.agents, + temporaryChat: interfaceConfig?.temporaryChat ?? defaults.temporaryChat, }); await updateAccessPermissions(roleName, { diff --git a/client/src/components/Chat/Input/AudioRecorder.tsx b/client/src/components/Chat/Input/AudioRecorder.tsx index 96e29ec50..a16d8870a 100644 --- a/client/src/components/Chat/Input/AudioRecorder.tsx +++ b/client/src/components/Chat/Input/AudioRecorder.tsx @@ -13,6 +13,7 @@ export default function AudioRecorder({ methods, textAreaRef, isSubmitting, + isTemporary = false, }: { isRTL: boolean; disabled: boolean; @@ -20,6 +21,7 @@ export default function AudioRecorder({ methods: ReturnType; textAreaRef: React.RefObject; isSubmitting: boolean; + isTemporary?: boolean; }) { const { setValue, reset } = methods; const localize = useLocalize(); @@ -76,7 +78,11 @@ export default function AudioRecorder({ if (isLoading === true) { return ; } - return ; + return ( + + ); }; return ( diff --git a/client/src/components/Chat/Input/ChatForm.tsx b/client/src/components/Chat/Input/ChatForm.tsx index 944a6ed6c..acfef0ad5 100644 --- a/client/src/components/Chat/Input/ChatForm.tsx +++ b/client/src/components/Chat/Input/ChatForm.tsx @@ -47,6 +47,7 @@ const ChatForm = ({ index = 0 }) => { const TextToSpeech = useRecoilValue(store.textToSpeech); const automaticPlayback = useRecoilValue(store.automaticPlayback); const maximizeChatSpace = useRecoilValue(store.maximizeChatSpace); + const isTemporary = useRecoilValue(store.isTemporary); const isSearching = useRecoilValue(store.isSearching); const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index)); @@ -146,6 +147,9 @@ const ChatForm = ({ index = 0 }) => { const baseClasses = cn( 'md:py-3.5 m-0 w-full resize-none bg-surface-tertiary py-[13px] placeholder-black/50 dark:placeholder-white/50 [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)]', isCollapsed ? 'max-h-[52px]' : 'max-h-[65vh] md:max-h-[75vh]', + isTemporary + ? 'bg-gray-600 text-white placeholder-white/20' + : 'bg-surface-tertiary placeholder-black/50 dark:placeholder-white/50', ); const uploadActive = endpointSupportsFiles && !isUploadDisabled; @@ -181,7 +185,12 @@ const ChatForm = ({ index = 0 }) => { /> )} -
+
{endpoint && ( @@ -234,6 +243,7 @@ const ChatForm = ({ index = 0 }) => { textAreaRef={textAreaRef} disabled={!!disableInputs} isSubmitting={isSubmitting} + isTemporary={isTemporary} /> )} {TextToSpeech && automaticPlayback && } diff --git a/client/src/components/Chat/Menus/BookmarkMenu.tsx b/client/src/components/Chat/Menus/BookmarkMenu.tsx index f4b804f5e..6616ba46c 100644 --- a/client/src/components/Chat/Menus/BookmarkMenu.tsx +++ b/client/src/components/Chat/Menus/BookmarkMenu.tsx @@ -28,6 +28,7 @@ const BookmarkMenu: FC = () => { const conversationId = conversation?.conversationId ?? ''; const updateConvoTags = useBookmarkSuccess(conversationId); const tags = conversation?.tags; + const isTemporary = conversation?.expiredAt != null; const menuId = useId(); const [isMenuOpen, setIsMenuOpen] = useState(false); @@ -139,6 +140,10 @@ const BookmarkMenu: FC = () => { return null; } + if (isTemporary) { + return null; + } + const renderButtonContent = () => { if (mutation.isLoading) { return ; diff --git a/client/src/components/Input/ModelSelect/Anthropic.tsx b/client/src/components/Input/ModelSelect/Anthropic.tsx index 56c373a0f..41678ac59 100644 --- a/client/src/components/Input/ModelSelect/Anthropic.tsx +++ b/client/src/components/Input/ModelSelect/Anthropic.tsx @@ -1,6 +1,7 @@ import { SelectDropDown, SelectDropDownPop } from '~/components/ui'; import type { TModelSelectProps } from '~/common'; import { cn, cardStyle } from '~/utils/'; +import { TemporaryChat } from './TemporaryChat'; export default function Anthropic({ conversation, @@ -19,8 +20,9 @@ export default function Anthropic({ showLabel={false} className={cn( cardStyle, - 'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer', + 'z-50 flex h-[40px] w-48 min-w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer', )} + footer={} /> ); } diff --git a/client/src/components/Input/ModelSelect/ChatGPT.tsx b/client/src/components/Input/ModelSelect/ChatGPT.tsx index e21c12ae7..3a088cd0f 100644 --- a/client/src/components/Input/ModelSelect/ChatGPT.tsx +++ b/client/src/components/Input/ModelSelect/ChatGPT.tsx @@ -1,5 +1,6 @@ import { SelectDropDown, SelectDropDownPop } from '~/components/ui'; import type { TModelSelectProps } from '~/common'; +import { TemporaryChat } from './TemporaryChat'; import { cn, cardStyle } from '~/utils/'; export default function ChatGPT({ @@ -26,8 +27,9 @@ export default function ChatGPT({ showLabel={false} className={cn( cardStyle, - 'min-w-48 z-50 flex h-[40px] w-60 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer', + 'z-50 flex h-[40px] w-60 min-w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer', )} + footer={} /> ); } diff --git a/client/src/components/Input/ModelSelect/Google.tsx b/client/src/components/Input/ModelSelect/Google.tsx index 83d898164..d9ab45f77 100644 --- a/client/src/components/Input/ModelSelect/Google.tsx +++ b/client/src/components/Input/ModelSelect/Google.tsx @@ -1,5 +1,6 @@ import { SelectDropDown, SelectDropDownPop } from '~/components/ui'; import type { TModelSelectProps } from '~/common'; +import { TemporaryChat } from './TemporaryChat'; import { cn, cardStyle } from '~/utils/'; export default function Google({ @@ -19,8 +20,9 @@ export default function Google({ showLabel={false} className={cn( cardStyle, - 'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer', + 'z-50 flex h-[40px] w-48 min-w-48 flex-none items-center justify-center px-4 ring-0 hover:cursor-pointer', )} + footer={} /> ); } diff --git a/client/src/components/Input/ModelSelect/OpenAI.tsx b/client/src/components/Input/ModelSelect/OpenAI.tsx index 1322a9940..c95e5b3cd 100644 --- a/client/src/components/Input/ModelSelect/OpenAI.tsx +++ b/client/src/components/Input/ModelSelect/OpenAI.tsx @@ -1,5 +1,6 @@ import { SelectDropDown, SelectDropDownPop } from '~/components/ui'; import type { TModelSelectProps } from '~/common'; +import { TemporaryChat } from './TemporaryChat'; import { cn, cardStyle } from '~/utils/'; export default function OpenAI({ @@ -19,8 +20,9 @@ export default function OpenAI({ showLabel={false} className={cn( cardStyle, - 'min-w-48 z-50 flex h-[40px] w-48 flex-none items-center justify-center px-4 hover:cursor-pointer', + 'z-50 flex h-[40px] w-48 min-w-48 flex-none items-center justify-center px-4 hover:cursor-pointer', )} + footer={} /> ); } diff --git a/client/src/components/Input/ModelSelect/TemporaryChat.tsx b/client/src/components/Input/ModelSelect/TemporaryChat.tsx new file mode 100644 index 000000000..9de47b1e6 --- /dev/null +++ b/client/src/components/Input/ModelSelect/TemporaryChat.tsx @@ -0,0 +1,61 @@ +import { useMemo } from 'react'; + +import { MessageCircleDashed } from 'lucide-react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { useGetStartupConfig } from 'librechat-data-provider/react-query'; +import { Constants, getConfigDefaults } from 'librechat-data-provider'; +import temporaryStore from '~/store/temporary'; +import { Switch } from '~/components/ui'; +import { cn } from '~/utils'; +import store from '~/store'; + +export const TemporaryChat = () => { + const { data: startupConfig } = useGetStartupConfig(); + const defaultInterface = getConfigDefaults().interface; + const [isTemporary, setIsTemporary] = useRecoilState(temporaryStore.isTemporary); + const conversation = useRecoilValue(store.conversationByIndex(0)) || undefined; + const conversationId = conversation?.conversationId ?? ''; + const interfaceConfig = useMemo( + () => startupConfig?.interface ?? defaultInterface, + [startupConfig], + ); + + if (!interfaceConfig.temporaryChat) { + return null; + } + + const isActiveConvo = Boolean( + conversation && + conversationId && + conversationId !== Constants.NEW_CONVO && + conversationId !== 'search', + ); + + const onClick = () => { + if (isActiveConvo) { + return; + } + setIsTemporary(!isTemporary); + }; + + return ( +
+
+
+ + Temporary Chat +
+
+ +
+
+
+ ); +}; diff --git a/client/src/components/ui/SelectDropDownPop.tsx b/client/src/components/ui/SelectDropDownPop.tsx index bf34fe756..3a4b1e581 100644 --- a/client/src/components/ui/SelectDropDownPop.tsx +++ b/client/src/components/ui/SelectDropDownPop.tsx @@ -18,6 +18,7 @@ type SelectDropDownProps = { showLabel?: boolean; iconSide?: 'left' | 'right'; renderOption?: () => React.ReactNode; + footer?: React.ReactNode; }; function SelectDropDownPop({ @@ -28,6 +29,7 @@ function SelectDropDownPop({ showAbove = false, showLabel = true, emptyTitle = false, + footer, }: SelectDropDownProps) { const localize = useLocalize(); const transitionProps = { className: 'top-full mt-3' }; @@ -78,9 +80,6 @@ function SelectDropDownPop({ 'min-w-[75px] font-normal', )} > - {/* {!showLabel && !emptyTitle && ( - {title}: - )} */} {typeof value !== 'string' && value ? value.label ?? '' : value ?? ''} @@ -124,6 +123,7 @@ function SelectDropDownPop({ /> ); })} + {footer}
diff --git a/client/src/hooks/Chat/useChatFunctions.ts b/client/src/hooks/Chat/useChatFunctions.ts index 24e74cf3b..3b7b11132 100644 --- a/client/src/hooks/Chat/useChatFunctions.ts +++ b/client/src/hooks/Chat/useChatFunctions.ts @@ -71,6 +71,7 @@ export default function useChatFunctions({ const setShowStopButton = useSetRecoilState(store.showStopButtonByIndex(index)); const setFilesToDelete = useSetFilesToDelete(); const getSender = useGetSender(); + const isTemporary = useRecoilValue(store.isTemporary); const queryClient = useQueryClient(); const { getExpiry } = useUserKey(conversation?.endpoint ?? ''); @@ -293,6 +294,7 @@ export default function useChatFunctions({ isContinued, isRegenerate, initialResponse, + isTemporary, }; if (isRegenerate) { diff --git a/client/src/hooks/SSE/useEventHandlers.ts b/client/src/hooks/SSE/useEventHandlers.ts index b3a57279b..73324d72e 100644 --- a/client/src/hooks/SSE/useEventHandlers.ts +++ b/client/src/hooks/SSE/useEventHandlers.ts @@ -275,7 +275,7 @@ export default function useEventHandlers({ const createdHandler = useCallback( (data: TResData, submission: EventSubmission) => { - const { messages, userMessage, isRegenerate = false } = submission; + const { messages, userMessage, isRegenerate = false, isTemporary = false } = submission; const initialResponse = { ...submission.initialResponse, parentMessageId: userMessage.messageId, @@ -317,6 +317,9 @@ export default function useEventHandlers({ return update; }); + if (isTemporary) { + return; + } queryClient.setQueryData([QueryKeys.allConversations], (convoData) => { if (!convoData) { return convoData; @@ -357,7 +360,12 @@ export default function useEventHandlers({ const finalHandler = useCallback( (data: TFinalResData, submission: EventSubmission) => { const { requestMessage, responseMessage, conversation, runMessages } = data; - const { messages, conversation: submissionConvo, isRegenerate = false } = submission; + const { + messages, + conversation: submissionConvo, + isRegenerate = false, + isTemporary = false, + } = submission; setShowStopButton(false); setCompleted((prev) => new Set(prev.add(submission.initialResponse.messageId))); @@ -401,6 +409,7 @@ export default function useEventHandlers({ if ( genTitle && isNewConvo && + !isTemporary && requestMessage && requestMessage.parentMessageId === Constants.NO_PARENT ) { diff --git a/client/src/hooks/useNewConvo.ts b/client/src/hooks/useNewConvo.ts index 7697bd80b..500206b18 100644 --- a/client/src/hooks/useNewConvo.ts +++ b/client/src/hooks/useNewConvo.ts @@ -38,6 +38,7 @@ const useNewConvo = (index = 0) => { const clearAllConversations = store.useClearConvoState(); const defaultPreset = useRecoilValue(store.defaultPreset); const { setConversation } = store.useCreateConversationAtom(index); + const [isTemporary, setIsTemporary] = useRecoilState(store.isTemporary); const [files, setFiles] = useRecoilState(store.filesByIndex(index)); const clearAllLatestMessages = store.useClearLatestMessages(`useNewConvo ${index}`); const setSubmission = useSetRecoilState(store.submissionByIndex(index)); @@ -195,6 +196,9 @@ const useNewConvo = (index = 0) => { keepAddedConvos?: boolean; } = {}) { pauseGlobalAudio(); + if (isTemporary) { + setIsTemporary(false); + } const templateConvoId = _template.conversationId ?? ''; const paramEndpoint = diff --git a/client/src/routes/ChatRoute.tsx b/client/src/routes/ChatRoute.tsx index f6190e232..2356e4396 100644 --- a/client/src/routes/ChatRoute.tsx +++ b/client/src/routes/ChatRoute.tsx @@ -14,13 +14,22 @@ import { getDefaultModelSpec, getModelSpecIconURL } from '~/utils'; import { ToolCallsMapProvider } from '~/Providers'; import ChatView from '~/components/Chat/ChatView'; import useAuthRedirect from './useAuthRedirect'; +import temporaryStore from '~/store/temporary'; import { Spinner } from '~/components/svg'; +import { useRecoilCallback } from 'recoil'; import store from '~/store'; export default function ChatRoute() { useHealthCheck(); const { data: startupConfig } = useGetStartupConfig(); const { isAuthenticated, user } = useAuthRedirect(); + const setIsTemporary = useRecoilCallback( + ({ set }) => + (value: boolean) => { + set(temporaryStore.isTemporary, value); + }, + [], + ); useAppStartup({ startupConfig, user }); const index = 0; @@ -141,6 +150,14 @@ export default function ChatRoute() { return null; } + const isTemporaryChat = conversation && conversation.expiredAt ? true : false; + + if (conversationId !== Constants.NEW_CONVO && !isTemporaryChat) { + setIsTemporary(false); + } else if (isTemporaryChat) { + setIsTemporary(isTemporaryChat); + } + return ( diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 70b8c22b8..59f12ae0f 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -11,6 +11,7 @@ import prompts from './prompts'; import lang from './language'; import settings from './settings'; import misc from './misc'; +import isTemporary from './temporary'; export default { ...artifacts, ...families, @@ -25,4 +26,5 @@ export default { ...lang, ...settings, ...misc, + ...isTemporary, }; diff --git a/client/src/store/temporary.ts b/client/src/store/temporary.ts new file mode 100644 index 000000000..b82bb789c --- /dev/null +++ b/client/src/store/temporary.ts @@ -0,0 +1,10 @@ +import { atom } from 'recoil'; + +const isTemporary = atom({ + key: 'isTemporary', + default: false, +}); + +export default { + isTemporary, +}; diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 738fafccc..3ba6c1154 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -455,6 +455,7 @@ export const intefaceSchema = z presets: z.boolean().optional(), prompts: z.boolean().optional(), agents: z.boolean().optional(), + temporaryChat: z.boolean().optional(), }) .default({ endpointsMenu: true, @@ -466,6 +467,7 @@ export const intefaceSchema = z bookmarks: true, prompts: true, agents: true, + temporaryChat: true, }); export type TInterfaceConfig = z.infer; diff --git a/packages/data-provider/src/createPayload.ts b/packages/data-provider/src/createPayload.ts index 5b73f102f..a03d7db03 100644 --- a/packages/data-provider/src/createPayload.ts +++ b/packages/data-provider/src/createPayload.ts @@ -3,7 +3,8 @@ import { EndpointURLs } from './config'; import * as s from './schemas'; export default function createPayload(submission: t.TSubmission) { - const { conversation, userMessage, endpointOption, isEdited, isContinued } = submission; + const { conversation, userMessage, endpointOption, isEdited, isContinued, isTemporary } = + submission; const { conversationId } = s.tConvoUpdateSchema.parse(conversation); const { endpoint, endpointType } = endpointOption as { endpoint: s.EModelEndpoint; @@ -23,6 +24,7 @@ export default function createPayload(submission: t.TSubmission) { ...endpointOption, isContinued: !!(isEdited && isContinued), conversationId, + isTemporary, }; return { server, payload }; diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index 6bfe788ab..8c984cb87 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -590,6 +590,8 @@ export const tConversationSchema = z.object({ greeting: z.string().optional(), spec: z.string().nullable().optional(), iconURL: z.string().nullable().optional(), + /* temporary chat */ + expiredAt: z.string().nullable().optional(), /** @deprecated */ resendImages: z.boolean().optional(), /** @deprecated */ diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 418d455a2..6d9cd87c8 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -44,6 +44,7 @@ export type TPayload = Partial & isContinued: boolean; conversationId: string | null; messages?: TMessages; + isTemporary: boolean; }; export type TSubmission = { @@ -53,6 +54,7 @@ export type TSubmission = { userMessage: TMessage; isEdited?: boolean; isContinued?: boolean; + isTemporary: boolean; messages: TMessage[]; isRegenerate?: boolean; conversationId?: string;