mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-08 11:38:51 +01:00
🅰️ feat: Dynamic Font Size (#3568)
* wip: general setup * added: translations for font-size * fix: prompts related linter errors and add theming * wip: font size selector * refactor: Update FontSizeSelector options display property * refactor: adjust Intersection Observer threshold and debounce rate * feat: Update selectedPrompt type in PromptForm to be optional * feat: dynamic font size * refactor: Update message font size in navigation bar * refactor: Update code analyze block styling * refactor: ProgressText dynamic font size * refactor: move FontSizeSelector component to Chat from General settings * fix: HoverButtons styling for better visibility * refactor: Update HoverButtons styling for better visibility --------- Co-authored-by: kraken <solodarken@gmail.com>
This commit is contained in:
parent
b390ba781f
commit
2bb0842650
44 changed files with 340 additions and 132 deletions
|
|
@ -54,11 +54,11 @@ export default function CodeAnalyze({
|
|||
onClick={() => setShowCode((prev) => !prev)}
|
||||
inProgressText="Analyzing"
|
||||
finishedText="Finished analyzing"
|
||||
hasInput={!!code?.length}
|
||||
hasInput={!!code.length}
|
||||
/>
|
||||
</div>
|
||||
{showCode && (
|
||||
<div className="mb-3 mt-0.5 overflow-hidden rounded-xl bg-black">
|
||||
<div className="code-analyze-block mb-3 mt-0.5 overflow-hidden rounded-xl bg-black">
|
||||
<MarkdownLite content={code ? `\`\`\`python\n${code}\n\`\`\`` : ''} />
|
||||
{logs && (
|
||||
<div className="bg-gray-700 p-4 text-xs">
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ const DisplayMessage = ({ text, isCreatedByUser, message, showCursor }: TDisplay
|
|||
<Container message={message}>
|
||||
<div
|
||||
className={cn(
|
||||
showCursor && !!text?.length ? 'result-streaming' : '',
|
||||
'markdown prose dark:prose-invert light w-full break-words',
|
||||
showCursor && !!text.length ? 'result-streaming' : '',
|
||||
'markdown prose message-content dark:prose-invert light w-full break-words',
|
||||
isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-100',
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ const DisplayMessage = ({ text, isCreatedByUser = false, message, showCursor }:
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
showCursor && !!text?.length ? 'result-streaming' : '',
|
||||
'markdown prose dark:prose-invert light w-full break-words',
|
||||
showCursor && !!text.length ? 'result-streaming' : '',
|
||||
'markdown prose message-content dark:prose-invert light w-full break-words',
|
||||
isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-70',
|
||||
)}
|
||||
>
|
||||
|
|
@ -58,14 +58,12 @@ export default function Part({
|
|||
// Access the value property
|
||||
return (
|
||||
<Container message={message}>
|
||||
<div className="markdown prose dark:prose-invert light dark:text-gray-70 my-1 w-full break-words">
|
||||
<DisplayMessage
|
||||
text={part[ContentTypes.TEXT].value}
|
||||
isCreatedByUser={message.isCreatedByUser}
|
||||
message={message}
|
||||
showCursor={showCursor}
|
||||
/>
|
||||
</div>
|
||||
<DisplayMessage
|
||||
text={part[ContentTypes.TEXT].value}
|
||||
isCreatedByUser={message.isCreatedByUser}
|
||||
message={message}
|
||||
showCursor={showCursor}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
} else if (
|
||||
|
|
@ -107,14 +105,12 @@ export default function Part({
|
|||
if (isSubmitting && showCursor) {
|
||||
return (
|
||||
<Container message={message}>
|
||||
<div className="markdown prose dark:prose-invert light dark:text-gray-70 my-1 w-full break-words">
|
||||
<DisplayMessage
|
||||
text={''}
|
||||
isCreatedByUser={message.isCreatedByUser}
|
||||
message={message}
|
||||
showCursor={showCursor}
|
||||
/>
|
||||
</div>
|
||||
<DisplayMessage
|
||||
text={''}
|
||||
isCreatedByUser={message.isCreatedByUser}
|
||||
message={message}
|
||||
showCursor={showCursor}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
import * as Popover from '@radix-ui/react-popover';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const wrapperClass =
|
||||
'progress-text-wrapper text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5';
|
||||
|
||||
const Wrapper = ({ popover, children }: { popover: boolean; children: React.ReactNode }) => {
|
||||
if (popover) {
|
||||
return (
|
||||
<div className="text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5">
|
||||
<div className={wrapperClass}>
|
||||
<Popover.Trigger asChild>
|
||||
<div
|
||||
className="absolute left-0 top-0 line-clamp-1 overflow-visible"
|
||||
className="progress-text-content absolute left-0 top-0 line-clamp-1 overflow-visible"
|
||||
style={{ opacity: 1, transform: 'none' }}
|
||||
data-projection-id="78"
|
||||
>
|
||||
|
|
@ -19,9 +22,9 @@ const Wrapper = ({ popover, children }: { popover: boolean; children: React.Reac
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5">
|
||||
<div className={wrapperClass}>
|
||||
<div
|
||||
className="absolute left-0 top-0 line-clamp-1 overflow-visible"
|
||||
className="progress-text-content absolute left-0 top-0 line-clamp-1 overflow-visible"
|
||||
style={{ opacity: 1, transform: 'none' }}
|
||||
data-projection-id="78"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -72,12 +72,12 @@ export default function HoverButtons({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="visible mt-0 flex justify-center gap-1 self-end text-gray-400 lg:justify-start">
|
||||
<div className="visible mt-0 flex justify-center gap-1 self-end text-gray-500 lg:justify-start">
|
||||
{TextToSpeech && <MessageAudio index={index} message={message} isLast={isLast} />}
|
||||
{isEditableEndpoint && (
|
||||
<button
|
||||
className={cn(
|
||||
'hover-button rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
'hover-button rounded-md p-1 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
isCreatedByUser ? '' : 'active',
|
||||
hideEditButton ? 'opacity-0' : '',
|
||||
isEditing ? 'active text-gray-700 dark:text-gray-200' : '',
|
||||
|
|
@ -93,7 +93,7 @@ export default function HoverButtons({
|
|||
)}
|
||||
<button
|
||||
className={cn(
|
||||
'ml-0 flex items-center gap-1.5 rounded-md p-1 text-xs text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
'ml-0 flex items-center gap-1.5 rounded-md p-1 text-xs hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
isSubmitting && isCreatedByUser ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||
)}
|
||||
|
|
@ -108,7 +108,7 @@ export default function HoverButtons({
|
|||
{regenerateEnabled ? (
|
||||
<button
|
||||
className={cn(
|
||||
'hover-button active rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
'hover-button active rounded-md p-1 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||
)}
|
||||
onClick={regenerate}
|
||||
|
|
@ -131,7 +131,7 @@ export default function HoverButtons({
|
|||
{continueSupported ? (
|
||||
<button
|
||||
className={cn(
|
||||
'hover-button active rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible',
|
||||
'hover-button active rounded-md p-1 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible',
|
||||
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||
)}
|
||||
onClick={handleContinue}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default function Message(props: TMessageProps) {
|
|||
<>
|
||||
<MessageContainer handleScroll={handleScroll}>
|
||||
{showSibling ? (
|
||||
<div className="m-auto my-2 flex justify-center p-4 py-2 text-base md:gap-6">
|
||||
<div className="m-auto my-2 flex justify-center p-4 py-2 md:gap-6">
|
||||
<div className="flex w-full flex-row flex-wrap justify-between gap-1 md:max-w-5xl md:flex-nowrap md:gap-2 lg:max-w-5xl xl:max-w-6xl">
|
||||
<MessageRender
|
||||
{...props}
|
||||
|
|
@ -58,7 +58,7 @@ export default function Message(props: TMessageProps) {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="m-auto justify-center p-4 py-2 text-base md:gap-6 ">
|
||||
<div className="m-auto justify-center p-4 py-2 md:gap-6 ">
|
||||
<MessageRender {...props} />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import Icon from '~/components/Chat/Messages/MessageIcon';
|
||||
import ContentParts from './Content/ContentParts';
|
||||
|
|
@ -8,6 +9,7 @@ import MultiMessage from './MultiMessage';
|
|||
import HoverButtons from './HoverButtons';
|
||||
import SubRow from './SubRow';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
export default function Message(props: TMessageProps) {
|
||||
const { message, siblingIdx, siblingCount, setSiblingIdx, currentEditId, setCurrentEditId } =
|
||||
|
|
@ -28,6 +30,7 @@ export default function Message(props: TMessageProps) {
|
|||
copyToClipboard,
|
||||
regenerateMessage,
|
||||
} = useMessageHelpers(props);
|
||||
const fontSize = useRecoilValue(store.fontSize);
|
||||
|
||||
const { content, children, messageId = null, isCreatedByUser, error, unfinished } = message ?? {};
|
||||
|
||||
|
|
@ -42,8 +45,8 @@ export default function Message(props: TMessageProps) {
|
|||
onWheel={handleScroll}
|
||||
onTouchMove={handleScroll}
|
||||
>
|
||||
<div className="m-auto justify-center p-4 py-2 text-base md:gap-6 ">
|
||||
<div className="group mx-auto flex flex-1 gap-3 text-base md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5">
|
||||
<div className="m-auto justify-center p-4 py-2 md:gap-6 ">
|
||||
<div className="group mx-auto flex flex-1 gap-3 md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5">
|
||||
<div className="relative flex flex-shrink-0 flex-col items-end">
|
||||
<div>
|
||||
<div className="pt-0.5">
|
||||
|
|
@ -54,10 +57,14 @@ export default function Message(props: TMessageProps) {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn('relative flex w-full flex-col', isCreatedByUser ? '' : 'agent-turn')}
|
||||
className={cn(
|
||||
'relative flex w-full flex-col',
|
||||
isCreatedByUser != null ? '' : 'agent-turn',
|
||||
)}
|
||||
>
|
||||
<div className="select-none font-semibold">
|
||||
{isCreatedByUser ? 'You' : (assistant && assistant?.name) ?? 'Assistant'}
|
||||
<div className={cn('select-none font-semibold', fontSize)}>
|
||||
{/* TODO: LOCALIZE */}
|
||||
{isCreatedByUser != null ? 'You' : (assistant && assistant.name) ?? 'Assistant'}
|
||||
</div>
|
||||
<div className="flex-col gap-1 md:gap-3">
|
||||
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import ScrollToBottom from '~/components/Messages/ScrollToBottom';
|
||||
import { useScreenshot, useMessageScrolling } from '~/hooks';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import MultiMessage from './MultiMessage';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
export default function MessagesView({
|
||||
messagesTree: _messagesTree,
|
||||
|
|
@ -13,6 +16,7 @@ export default function MessagesView({
|
|||
messagesTree?: TMessage[] | null;
|
||||
Header?: ReactNode;
|
||||
}) {
|
||||
const fontSize = useRecoilValue(store.fontSize);
|
||||
const { screenshotTargetRef } = useScreenshot();
|
||||
const [currentEditId, setCurrentEditId] = useState<number | string | null>(-1);
|
||||
|
||||
|
|
@ -39,9 +43,14 @@ export default function MessagesView({
|
|||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col pb-9 text-sm dark:bg-transparent">
|
||||
<div className="flex flex-col pb-9 dark:bg-transparent">
|
||||
{(_messagesTree && _messagesTree.length == 0) || _messagesTree === null ? (
|
||||
<div className="flex w-full items-center justify-center gap-1 bg-gray-50 p-3 text-sm text-gray-500 dark:border-gray-800/50 dark:bg-gray-800 dark:text-gray-300">
|
||||
<div
|
||||
className={cn(
|
||||
'flex w-full items-center justify-center gap-1 bg-gray-50 p-3 text-gray-500 dark:border-gray-800/50 dark:bg-gray-800 dark:text-gray-300',
|
||||
fontSize,
|
||||
)}
|
||||
>
|
||||
Nothing found
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import store from '~/store';
|
|||
|
||||
export default function Message({ message }: Pick<TMessageProps, 'message'>) {
|
||||
const UsernameDisplay = useRecoilValue<boolean>(store.UsernameDisplay);
|
||||
const fontSize = useRecoilValue(store.fontSize);
|
||||
const { user } = useAuthContext();
|
||||
const localize = useLocalize();
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ export default function Message({ message }: Pick<TMessageProps, 'message'>) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const { isCreatedByUser } = message ?? {};
|
||||
const { isCreatedByUser } = message;
|
||||
|
||||
let messageLabel = '';
|
||||
if (isCreatedByUser) {
|
||||
|
|
@ -30,8 +31,8 @@ export default function Message({ message }: Pick<TMessageProps, 'message'>) {
|
|||
return (
|
||||
<>
|
||||
<div className="text-token-text-primary w-full border-0 bg-transparent dark:border-0 dark:bg-transparent">
|
||||
<div className="m-auto justify-center p-4 py-2 text-base md:gap-6 ">
|
||||
<div className="final-completion group mx-auto flex flex-1 gap-3 text-base md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5">
|
||||
<div className="m-auto justify-center p-4 py-2 md:gap-6 ">
|
||||
<div className="final-completion group mx-auto flex flex-1 gap-3 md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5">
|
||||
<div className="relative flex flex-shrink-0 flex-col items-end">
|
||||
<div>
|
||||
<div className="pt-0.5">
|
||||
|
|
@ -44,7 +45,7 @@ export default function Message({ message }: Pick<TMessageProps, 'message'>) {
|
|||
<div
|
||||
className={cn('relative flex w-11/12 flex-col', isCreatedByUser ? '' : 'agent-turn')}
|
||||
>
|
||||
<div className="select-none font-semibold">{messageLabel}</div>
|
||||
<div className={cn('select-none font-semibold', fontSize)}>{messageLabel}</div>
|
||||
<div className="flex-col gap-1 md:gap-3">
|
||||
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||
<SearchContent message={message} />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useMessageActions } from '~/hooks';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useCallback, useMemo, memo } from 'react';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import MessageContent from '~/components/Chat/Messages/Content/MessageContent';
|
||||
|
|
@ -9,7 +9,9 @@ import HoverButtons from '~/components/Chat/Messages/HoverButtons';
|
|||
import Icon from '~/components/Chat/Messages/MessageIcon';
|
||||
import { Plugin } from '~/components/Messages/Content';
|
||||
import SubRow from '~/components/Chat/Messages/SubRow';
|
||||
import { useMessageActions } from '~/hooks';
|
||||
import { cn, logger } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
type MessageRenderProps = {
|
||||
message?: TMessage;
|
||||
|
|
@ -21,7 +23,7 @@ type MessageRenderProps = {
|
|||
'currentEditId' | 'setCurrentEditId' | 'siblingIdx' | 'setSiblingIdx' | 'siblingCount'
|
||||
>;
|
||||
|
||||
const MessageRender = React.memo(
|
||||
const MessageRender = memo(
|
||||
({
|
||||
isCard,
|
||||
siblingIdx,
|
||||
|
|
@ -54,6 +56,7 @@ const MessageRender = React.memo(
|
|||
setCurrentEditId,
|
||||
});
|
||||
|
||||
const fontSize = useRecoilValue(store.fontSize);
|
||||
const handleRegenerateMessage = useCallback(() => regenerateMessage(), [regenerateMessage]);
|
||||
const { isCreatedByUser, error, unfinished } = msg ?? {};
|
||||
const isLast = useMemo(
|
||||
|
|
@ -81,7 +84,7 @@ const MessageRender = React.memo(
|
|||
<div
|
||||
aria-label={`message-${msg.depth}-${msg.messageId}`}
|
||||
className={cn(
|
||||
'final-completion group mx-auto flex flex-1 gap-3 text-base',
|
||||
'final-completion group mx-auto flex flex-1 gap-3',
|
||||
isCard === true
|
||||
? 'relative w-full gap-1 rounded-lg border border-border-medium bg-surface-primary-alt p-2 md:w-1/2 md:gap-3 md:p-4'
|
||||
: 'md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5',
|
||||
|
|
@ -113,7 +116,7 @@ const MessageRender = React.memo(
|
|||
<div
|
||||
className={cn('relative flex w-11/12 flex-col', msg.isCreatedByUser ? '' : 'agent-turn')}
|
||||
>
|
||||
<h2 className="select-none font-semibold">{messageLabel}</h2>
|
||||
<h2 className={cn('select-none font-semibold', fontSize)}>{messageLabel}</h2>
|
||||
<div className="flex-col gap-1 md:gap-3">
|
||||
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||
{msg.plugin && <Plugin plugin={msg.plugin} />}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue