💬 feat: move Temporary Chat to the Header (#6646)

* 🚀 feat: Add Temporary Chat feature with badge toggle functionality

* style: update header button

* fix: Integrate resetChatBadges functionality into useNewConvo hook following rules of react

* fix: Adjust margin logic in ChatForm for better layout handling on existing conversations

* fix: Refine margin logic in ChatForm to improve layout during message submission

* fix: Update TemporaryChat component to not render  when message is submitting

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2025-04-01 09:50:12 +02:00 committed by GitHub
parent a5154e1349
commit cd7cdaa703
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 111 additions and 21 deletions

View file

@ -8,7 +8,9 @@ import { useGetStartupConfig } from '~/data-provider';
import ExportAndShareMenu from './ExportAndShareMenu';
import { useMediaQuery, useHasAccess } from '~/hooks';
import BookmarkMenu from './Menus/BookmarkMenu';
import { TemporaryChat } from './TemporaryChat';
import AddMultiConvo from './AddMultiConvo';
const defaultInterface = getConfigDefaults().interface;
export default function Header() {
@ -42,13 +44,21 @@ export default function Header() {
{hasAccessToBookmarks === true && <BookmarkMenu />}
{hasAccessToMultiConvo === true && <AddMultiConvo />}
{isSmallScreen && (
<ExportAndShareMenu
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
/>
<>
<ExportAndShareMenu
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
/>
<TemporaryChat />
</>
)}
</div>
{!isSmallScreen && (
<ExportAndShareMenu isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false} />
<div className="flex items-center gap-2">
<ExportAndShareMenu
isSharedButtonEnabled={startupConfig?.sharedLinksEnabled ?? false}
/>
<TemporaryChat />
</div>
)}
</div>
{/* Empty div for spacing */}

View file

@ -9,9 +9,9 @@ import React, {
} from 'react';
import { useRecoilValue, useRecoilCallback } from 'recoil';
import type { LucideIcon } from 'lucide-react';
import type { BadgeItem } from '~/common';
import { useChatBadges } from '~/hooks';
import { Badge } from '~/components/ui';
import { BadgeItem } from '~/common';
import store from '~/store';
interface BadgeRowProps {

View file

@ -1,7 +1,7 @@
import { memo, useRef, useMemo, useEffect, useState, useCallback } from 'react';
import { useWatch } from 'react-hook-form';
import { useRecoilState, useRecoilValue } from 'recoil';
import { isAssistantsEndpoint } from 'librechat-data-provider';
import { Constants, isAssistantsEndpoint } from 'librechat-data-provider';
import {
useChatContext,
useChatFormContext,
@ -50,6 +50,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
const automaticPlayback = useRecoilValue(store.automaticPlayback);
const maximizeChatSpace = useRecoilValue(store.maximizeChatSpace);
const centerFormOnLanding = useRecoilValue(store.centerFormOnLanding);
const isTemporary = useRecoilValue(store.isTemporary);
const [badges, setBadges] = useRecoilState(store.chatBadges);
const [isEditingBadges, setIsEditingBadges] = useRecoilState(store.isEditingBadges);
@ -180,7 +181,7 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
const baseClasses = useMemo(
() =>
cn(
'md:py-3.5 m-0 w-full resize-none py-[13px] bg-surface-chat placeholder-black/50 dark:placeholder-white/50 [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)]',
'md:py-3.5 m-0 w-full resize-none py-[13px] placeholder-black/50 bg-transparent dark:placeholder-white/50 [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)]',
isCollapsed ? 'max-h-[52px]' : 'max-h-[45vh] md:max-h-[55vh]',
isMoreThanThreeRows ? 'pl-5' : 'px-5',
),
@ -191,9 +192,13 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
<form
onSubmit={methods.handleSubmit(submitMessage)}
className={cn(
'mx-auto flex flex-row gap-3 transition-all duration-200 sm:px-2',
'mx-auto flex flex-row gap-3 sm:px-2',
maximizeChatSpace ? 'w-full max-w-full' : 'md:max-w-3xl xl:max-w-4xl',
centerFormOnLanding ? 'sm:mb-28' : 'sm:mb-10',
centerFormOnLanding &&
(!conversation?.conversationId || conversation?.conversationId === Constants.NEW_CONVO) &&
!isSubmitting
? 'transition-all duration-200 sm:mb-28'
: 'sm:mb-10',
)}
>
<div className="relative flex h-full flex-1 items-stretch md:flex-col">
@ -219,8 +224,11 @@ const ChatForm = memo(({ index = 0 }: { index?: number }) => {
<div
onClick={handleContainerClick}
className={cn(
'relative flex w-full flex-grow flex-col overflow-hidden rounded-t-3xl border border-border-medium bg-surface-chat pb-4 text-text-primary transition-all duration-200 sm:rounded-3xl sm:pb-0',
'relative flex w-full flex-grow flex-col overflow-hidden rounded-t-3xl border pb-4 text-text-primary transition-all duration-200 sm:rounded-3xl sm:pb-0',
isTextAreaFocused ? 'shadow-lg' : 'shadow-md',
isTemporary
? 'border-violet-800/60 bg-violet-950/10'
: 'border-border-light bg-surface-chat',
)}
>
<TextareaHeader addedConvo={addedConvo} setAddedConvo={setAddedConvo} />

View file

@ -0,0 +1,69 @@
import React from 'react';
import { motion } from 'framer-motion';
import { MessageCircleDashed } from 'lucide-react';
import { useRecoilState, useRecoilCallback } from 'recoil';
import { TooltipAnchor } from '~/components/ui';
import { useChatContext } from '~/Providers';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
import store from '~/store';
export function TemporaryChat() {
const localize = useLocalize();
const [isTemporary, setIsTemporary] = useRecoilState(store.isTemporary);
const { conversation, isSubmitting } = useChatContext();
const temporaryBadge = {
id: 'temporary',
icon: MessageCircleDashed,
label: 'com_ui_temporary' as const,
atom: store.isTemporary,
isAvailable: true,
};
const handleBadgeToggle = useRecoilCallback(
() => () => {
setIsTemporary(!isTemporary);
},
[isTemporary],
);
if (
(Array.isArray(conversation?.messages) && conversation.messages.length >= 1) ||
isSubmitting
) {
return null;
}
return (
<div className="relative flex flex-wrap items-center gap-2">
<div className="badge-icon h-full">
<TooltipAnchor
description={localize(temporaryBadge.label)}
render={
<motion.button
onClick={handleBadgeToggle}
className={cn(
'inline-flex size-10 flex-shrink-0 items-center justify-center rounded-lg border border-border-light text-text-primary transition-all ease-in-out hover:bg-surface-tertiary',
isTemporary
? 'bg-surface-active shadow-md'
: 'bg-transparent shadow-sm hover:bg-surface-hover hover:shadow-md',
'active:scale-95 active:shadow-inner',
)}
transition={{ type: 'tween', duration: 0.1, ease: 'easeOut' }}
>
{temporaryBadge.icon && (
<temporaryBadge.icon
className={cn(
'relative h-5 w-5 md:h-4 md:w-4',
!temporaryBadge.label && 'mx-auto',
)}
/>
)}
</motion.button>
}
/>
</div>
</div>
);
}

View file

@ -5,21 +5,22 @@ import type { BadgeItem } from '~/common';
import { useLocalize } from '~/hooks';
import store from '~/store';
const badgeConfig = [
{
id: '1',
icon: MessageCircleDashed,
label: 'com_ui_temporary',
atom: store.isTemporary,
},
interface ChatBadgeConfig {
id: string;
icon: typeof Box;
label: string;
atom?: any;
}
const badgeConfig: ReadonlyArray<ChatBadgeConfig> = [
// {
// id: '2',
// id: '1',
// icon: Box,
// label: 'com_ui_artifacts',
// atom: store.codeArtifacts,
// },
// TODO: add more badges here (missing store atoms)
] as const;
];
export default function useChatBadges(): BadgeItem[] {
const localize = useLocalize();

View file

@ -50,6 +50,7 @@ const useNewConvo = (index = 0) => {
const assistantsListMap = useAssistantListMap();
const { pauseGlobalAudio } = usePauseGlobalAudio(index);
const saveDrafts = useRecoilValue<boolean>(store.saveDrafts);
const resetBadges = useResetChatBadges();
const { mutateAsync } = useDeleteFilesMutation({
onSuccess: () => {
@ -198,7 +199,7 @@ const useNewConvo = (index = 0) => {
} = {}) {
pauseGlobalAudio();
if (!saveBadgesState) {
useResetChatBadges();
resetBadges();
}
const templateConvoId = _template.conversationId ?? '';
@ -278,6 +279,7 @@ const useNewConvo = (index = 0) => {
files,
setFiles,
mutateAsync,
resetBadges,
],
);

View file

@ -810,7 +810,7 @@
"com_ui_storage": "Storage",
"com_ui_submit": "Submit",
"com_ui_teach_or_explain": "Learning",
"com_ui_temporary": "Temporary",
"com_ui_temporary": "Temporary Chat",
"com_ui_terms_and_conditions": "Terms and Conditions",
"com_ui_terms_of_service": "Terms of service",
"com_ui_thinking": "Thinking...",