mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00
💬 feat: Temporary Chats (#5493)
* 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
This commit is contained in:
parent
5f9543f6fc
commit
8c404ae056
24 changed files with 185 additions and 13 deletions
|
@ -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(),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 });
|
||||
|
||||
|
|
|
@ -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 });
|
||||
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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<typeof useChatFormContext>;
|
||||
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||
isSubmitting: boolean;
|
||||
isTemporary?: boolean;
|
||||
}) {
|
||||
const { setValue, reset } = methods;
|
||||
const localize = useLocalize();
|
||||
|
@ -76,7 +78,11 @@ export default function AudioRecorder({
|
|||
if (isLoading === true) {
|
||||
return <Spinner className="stroke-gray-700 dark:stroke-gray-300" />;
|
||||
}
|
||||
return <ListeningIcon className="stroke-gray-700 dark:stroke-gray-300" />;
|
||||
return (
|
||||
<ListeningIcon
|
||||
className={cn(isTemporary ? 'stroke-white' : 'stroke-gray-700 dark:stroke-gray-300')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -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 }) => {
|
|||
/>
|
||||
)}
|
||||
<PromptsCommand index={index} textAreaRef={textAreaRef} submitPrompt={submitPrompt} />
|
||||
<div className="transitional-all relative flex w-full flex-grow flex-col overflow-hidden rounded-3xl bg-surface-tertiary text-text-primary duration-200">
|
||||
<div
|
||||
className={cn(
|
||||
'transitional-all relative flex w-full flex-grow flex-col overflow-hidden rounded-3xl text-text-primary ',
|
||||
isTemporary ? 'text-white' : 'duration-200',
|
||||
)}
|
||||
>
|
||||
<TextareaHeader addedConvo={addedConvo} setAddedConvo={setAddedConvo} />
|
||||
<FileFormWrapper disableInputs={disableInputs}>
|
||||
{endpoint && (
|
||||
|
@ -234,6 +243,7 @@ const ChatForm = ({ index = 0 }) => {
|
|||
textAreaRef={textAreaRef}
|
||||
disabled={!!disableInputs}
|
||||
isSubmitting={isSubmitting}
|
||||
isTemporary={isTemporary}
|
||||
/>
|
||||
)}
|
||||
{TextToSpeech && automaticPlayback && <StreamAudio index={index} />}
|
||||
|
|
|
@ -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 <Spinner aria-label="Spinner" />;
|
||||
|
|
|
@ -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={<TemporaryChat />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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={<TemporaryChat />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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={<TemporaryChat />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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={<TemporaryChat />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
61
client/src/components/Input/ModelSelect/TemporaryChat.tsx
Normal file
61
client/src/components/Input/ModelSelect/TemporaryChat.tsx
Normal file
|
@ -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 (
|
||||
<div className="sticky bottom-0 border-t border-gray-200 bg-white px-6 py-4 dark:border-gray-700 dark:bg-gray-700">
|
||||
<div className="flex items-center">
|
||||
<div className={cn('flex flex-1 items-center gap-2', isActiveConvo && 'opacity-40')}>
|
||||
<MessageCircleDashed className="icon-sm" />
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Temporary Chat</span>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
<Switch
|
||||
id="enableUserMsgMarkdown"
|
||||
checked={isTemporary}
|
||||
onCheckedChange={onClick}
|
||||
disabled={isActiveConvo}
|
||||
className="ml-4"
|
||||
data-testid="enableUserMsgMarkdown"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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 && (
|
||||
<span className="text-xs text-gray-700 dark:text-gray-500">{title}:</span>
|
||||
)} */}
|
||||
{typeof value !== 'string' && value ? value.label ?? '' : value ?? ''}
|
||||
</span>
|
||||
</span>
|
||||
|
@ -124,6 +123,7 @@ function SelectDropDownPop({
|
|||
/>
|
||||
);
|
||||
})}
|
||||
{footer}
|
||||
</Content>
|
||||
</Portal>
|
||||
</div>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<ConversationData>([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
|
||||
) {
|
||||
|
|
|
@ -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<TSubmission | null>(store.submissionByIndex(index));
|
||||
|
@ -195,6 +196,9 @@ const useNewConvo = (index = 0) => {
|
|||
keepAddedConvos?: boolean;
|
||||
} = {}) {
|
||||
pauseGlobalAudio();
|
||||
if (isTemporary) {
|
||||
setIsTemporary(false);
|
||||
}
|
||||
|
||||
const templateConvoId = _template.conversationId ?? '';
|
||||
const paramEndpoint =
|
||||
|
|
|
@ -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 (
|
||||
<ToolCallsMapProvider conversationId={conversation.conversationId ?? ''}>
|
||||
<ChatView index={index} />
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
10
client/src/store/temporary.ts
Normal file
10
client/src/store/temporary.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { atom } from 'recoil';
|
||||
|
||||
const isTemporary = atom<boolean>({
|
||||
key: 'isTemporary',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export default {
|
||||
isTemporary,
|
||||
};
|
|
@ -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<typeof intefaceSchema>;
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -44,6 +44,7 @@ export type TPayload = Partial<TMessage> &
|
|||
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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue