Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/models/Conversation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -172,6 +183,7 @@ module.exports = {
Conversation.findOne({
user,
conversationId: convo.conversationId,
$or: [{ expiredAt: { $exists: false } }, { expiredAt: null }],
}).lean(),
),
);
Expand Down
9 changes: 9 additions & 0 deletions api/models/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions api/models/schema/convoSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const convoSchema = mongoose.Schema(
files: {
type: [String],
},
expiredAt: {
type: Date,
},
},
{ timestamps: true },
);
Expand All @@ -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 });

Expand Down
5 changes: 4 additions & 1 deletion api/models/schema/messageSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ const messageSchema = mongoose.Schema(
default: undefined,
},
*/
expiredAt: {
type: Date,
},
},
{ timestamps: true },
);
Expand All @@ -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 });

Expand Down
1 change: 1 addition & 0 deletions api/server/services/start/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down
8 changes: 7 additions & 1 deletion client/src/components/Chat/Input/AudioRecorder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ export default function AudioRecorder({
methods,
textAreaRef,
isSubmitting,
isTemporary = false,
}: {
isRTL: boolean;
disabled: boolean;
ask: (data: { text: string }) => void;
methods: ReturnType<typeof useChatFormContext>;
textAreaRef: React.RefObject<HTMLTextAreaElement>;
isSubmitting: boolean;
isTemporary?: boolean;
}) {
const { setValue, reset } = methods;
const localize = useLocalize();
Expand Down Expand Up @@ -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 (
Expand Down
12 changes: 11 additions & 1 deletion client/src/components/Chat/Input/ChatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 && (
Expand Down Expand Up @@ -234,6 +243,7 @@ const ChatForm = ({ index = 0 }) => {
textAreaRef={textAreaRef}
disabled={!!disableInputs}
isSubmitting={isSubmitting}
isTemporary={isTemporary}
/>
)}
{TextToSpeech && automaticPlayback && <StreamAudio index={index} />}
Expand Down
5 changes: 5 additions & 0 deletions client/src/components/Chat/Menus/BookmarkMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -139,6 +140,10 @@ const BookmarkMenu: FC = () => {
return null;
}

if (isTemporary) {
return null;
}

const renderButtonContent = () => {
if (mutation.isLoading) {
return <Spinner aria-label="Spinner" />;
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/Input/ModelSelect/Anthropic.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 />}
/>
);
}
4 changes: 3 additions & 1 deletion client/src/components/Input/ModelSelect/ChatGPT.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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 />}
/>
);
}
4 changes: 3 additions & 1 deletion client/src/components/Input/ModelSelect/Google.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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 />}
/>
);
}
4 changes: 3 additions & 1 deletion client/src/components/Input/ModelSelect/OpenAI.tsx
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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 changes: 61 additions & 0 deletions client/src/components/Input/ModelSelect/TemporaryChat.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
};
6 changes: 3 additions & 3 deletions client/src/components/ui/SelectDropDownPop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type SelectDropDownProps = {
showLabel?: boolean;
iconSide?: 'left' | 'right';
renderOption?: () => React.ReactNode;
footer?: React.ReactNode;
};

function SelectDropDownPop({
Expand All @@ -28,6 +29,7 @@ function SelectDropDownPop({
showAbove = false,
showLabel = true,
emptyTitle = false,
footer,
}: SelectDropDownProps) {
const localize = useLocalize();
const transitionProps = { className: 'top-full mt-3' };
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -124,6 +123,7 @@ function SelectDropDownPop({
/>
);
})}
{footer}
</Content>
</Portal>
</div>
Expand Down
2 changes: 2 additions & 0 deletions client/src/hooks/Chat/useChatFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? '');
Expand Down Expand Up @@ -293,6 +294,7 @@ export default function useChatFunctions({
isContinued,
isRegenerate,
initialResponse,
isTemporary,
};

if (isRegenerate) {
Expand Down
Loading
Loading