🅰️ 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:
Danny Avila 2024-08-07 14:23:33 -04:00 committed by GitHub
parent b390ba781f
commit 2bb0842650
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 340 additions and 132 deletions

View file

@ -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">

View file

@ -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',
)}
>

View file

@ -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>
);
}

View file

@ -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"
>

View file

@ -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}

View file

@ -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>
)}

View file

@ -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">

View file

@ -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>
) : (

View file

@ -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} />

View file

@ -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} />}

View file

@ -1,6 +1,7 @@
import { memo } from 'react';
import * as Tabs from '@radix-ui/react-tabs';
import { SettingsTabValues } from 'librechat-data-provider';
import FontSizeSelector from './FontSizeSelector';
import SendMessageKeyEnter from './EnterToSend';
import ShowCodeSwitch from './ShowCodeSwitch';
import { ForkSettings } from './ForkSettings';
@ -12,11 +13,14 @@ function Chat() {
<Tabs.Content value={SettingsTabValues.CHAT} role="tabpanel" className="md: w-full">
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<SendMessageKeyEnter />
<FontSizeSelector />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ChatDirection />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<SendMessageKeyEnter />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ShowCodeSwitch />
</div>

View file

@ -0,0 +1,37 @@
import { useRecoilState } from 'recoil';
import { Dropdown } from '~/components/ui';
import { applyFontSize } from '~/utils';
import { useLocalize } from '~/hooks';
import store from '~/store';
export default function FontSizeSelector() {
const [fontSize, setFontSize] = useRecoilState(store.fontSize);
const localize = useLocalize();
const handleChange = (val: string) => {
setFontSize(val);
applyFontSize(val);
};
const options = [
{ value: 'text-xs', display: localize('com_nav_font_size_xs') },
{ value: 'text-sm', display: localize('com_nav_font_size_sm') },
{ value: 'text-base', display: localize('com_nav_font_size_base') },
{ value: 'text-lg', display: localize('com_nav_font_size_lg') },
{ value: 'text-xl', display: localize('com_nav_font_size_xl') },
];
return (
<div className="flex w-full items-center justify-between">
<div>{localize('com_nav_font_size')}</div>
<Dropdown
value={fontSize}
options={options}
onChange={handleChange}
testId="font-size-selector"
sizeClasses="w-[150px]"
anchor="bottom start"
/>
</div>
);
}

View file

@ -6,10 +6,10 @@ import type { TDangerButtonProps } from '~/common';
import { ThemeContext, useLocalize, useLocalStorage } from '~/hooks';
import HideSidePanelSwitch from './HideSidePanelSwitch';
import AutoScrollSwitch from './AutoScrollSwitch';
import ArchivedChats from './ArchivedChats';
import { Dropdown } from '~/components/ui';
import DangerButton from '../DangerButton';
import store from '~/store';
import ArchivedChats from './ArchivedChats';
export const ThemeSelector = ({
theme,
@ -156,7 +156,7 @@ function General() {
className="w-full md:min-h-[271px]"
ref={contentRef}
>
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
<div className="flex flex-col gap-3 text-sm text-text-primary">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
<ThemeSelector theme={theme} onChange={changeTheme} />
</div>

View file

@ -1,9 +1,9 @@
import { useRecoilState, useSetRecoilState } from 'recoil';
import { PromptsEditorMode } from '~/common';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
import store from '~/store';
const { PromptsEditorMode, promptsEditorMode, alwaysMakeProd } = store;
const { promptsEditorMode, alwaysMakeProd } = store;
const AdvancedSwitch = () => {
const localize = useLocalize();

View file

@ -5,11 +5,12 @@ import { Controller, useFormContext, useFormState } from 'react-hook-form';
import AlwaysMakeProd from '~/components/Prompts/Groups/AlwaysMakeProd';
import { SaveIcon, CrossIcon } from '~/components/svg';
import { TextareaAutosize } from '~/components/ui';
import { PromptsEditorMode } from '~/common';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
import store from '~/store';
const { PromptsEditorMode, promptsEditorMode } = store;
const { promptsEditorMode } = store;
type Props = {
name: string;
@ -22,17 +23,18 @@ const PromptEditor: React.FC<Props> = ({ name, isEditing, setIsEditing }) => {
const { control } = useFormContext();
const editorMode = useRecoilValue(promptsEditorMode);
const { dirtyFields } = useFormState({ control: control });
const { prompt } = dirtyFields as { prompt?: string };
const EditorIcon = useMemo(() => {
if (isEditing && !dirtyFields.prompt) {
if (isEditing && prompt?.length == null) {
return CrossIcon;
}
return isEditing ? SaveIcon : EditIcon;
}, [isEditing, dirtyFields.prompt]);
}, [isEditing, prompt]);
return (
<div>
<h2 className="flex items-center justify-between rounded-t-lg border border-gray-300 py-2 pl-4 text-base font-semibold dark:border-gray-600 dark:text-gray-200">
<h2 className="flex items-center justify-between rounded-t-lg border border-border-medium py-2 pl-4 text-base font-semibold text-text-primary">
{localize('com_ui_prompt_text')}
<div className="flex flex-row gap-6">
{editorMode === PromptsEditorMode.ADVANCED && (
@ -49,14 +51,21 @@ const PromptEditor: React.FC<Props> = ({ name, isEditing, setIsEditing }) => {
</div>
</h2>
<div
role="button"
className={cn(
'group relative min-h-32 rounded-b-lg border border-gray-300 p-4 transition-all duration-150 hover:opacity-90 dark:border-gray-600',
'min-h-[8rem] w-full rounded-b-lg border border-border-medium p-4 transition-all duration-150',
{ 'cursor-pointer hover:bg-gray-100/50 dark:hover:bg-gray-100/10': !isEditing },
)}
onClick={() => !isEditing && setIsEditing(true)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
!isEditing && setIsEditing(true);
}
}}
tabIndex={0}
>
{!isEditing && (
<EditIcon className="icon-xl absolute inset-0 m-auto hidden opacity-25 group-hover:block dark:text-gray-200" />
<EditIcon className="icon-xl absolute inset-0 m-auto hidden text-text-primary opacity-25 group-hover:block" />
)}
<Controller
name={name}
@ -65,12 +74,20 @@ const PromptEditor: React.FC<Props> = ({ name, isEditing, setIsEditing }) => {
isEditing ? (
<TextareaAutosize
{...field}
className="w-full rounded border border-gray-300 bg-transparent px-2 py-1 focus:outline-none dark:border-gray-600 dark:text-gray-200"
className="w-full rounded border border-border-medium bg-transparent px-2 py-1 text-text-primary focus:outline-none"
minRows={3}
onBlur={() => setIsEditing(false)}
onKeyDown={(e) => {
if (e.key === 'Escape') {
e.preventDefault();
setIsEditing(false);
}
}}
/>
) : (
<pre className="block break-words px-2 py-1 dark:text-gray-200">{field.value}</pre>
<pre className="block h-full w-full whitespace-pre-wrap break-words px-2 py-1 text-left text-text-primary">
{field.value}
</pre>
)
}
/>

View file

@ -5,7 +5,7 @@ import { useForm, FormProvider } from 'react-hook-form';
import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
import { useNavigate, useParams, useOutletContext } from 'react-router-dom';
import { PermissionTypes, Permissions, SystemRoles } from 'librechat-data-provider';
import type { TCreatePrompt } from 'librechat-data-provider';
import type { TCreatePrompt, TPrompt } from 'librechat-data-provider';
import {
useGetPrompts,
useCreatePrompt,
@ -22,6 +22,7 @@ import { Button, Skeleton } from '~/components/ui';
import PromptVariables from './PromptVariables';
import { useToastContext } from '~/Providers';
import PromptVersions from './PromptVersions';
import { PromptsEditorMode } from '~/common';
import DeleteConfirm from './DeleteVersion';
import PromptDetails from './PromptDetails';
import { findPromptGroup } from '~/utils';
@ -33,7 +34,7 @@ import PromptName from './PromptName';
import Command from './Command';
import store from '~/store';
const { PromptsEditorMode, promptsEditorMode } = store;
const { promptsEditorMode } = store;
const PromptForm = () => {
const params = useParams();
@ -55,7 +56,10 @@ const PromptForm = () => {
const [initialLoad, setInitialLoad] = useState(true);
const [selectionIndex, setSelectionIndex] = useState<number>(0);
const isOwner = useMemo(() => user?.id === group?.author, [user, group]);
const selectedPrompt = useMemo(() => prompts[selectionIndex], [prompts, selectionIndex]);
const selectedPrompt = useMemo(
() => prompts[selectionIndex] as TPrompt | undefined,
[prompts, selectionIndex],
);
const hasShareAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
@ -229,7 +233,7 @@ const PromptForm = () => {
<Skeleton className="mb-1 flex h-10 w-32 flex-row items-center font-bold sm:text-xl md:mb-0 md:h-12 md:text-2xl" />
) : (
<PromptName
name={group?.name}
name={group.name}
onSave={(value) => {
if (!group) {
return console.warn('Group not found');
@ -241,11 +245,11 @@ const PromptForm = () => {
<div className="flex h-10 flex-row gap-x-2">
<CategorySelector
className="w-48 md:w-56"
currentCategory={group?.category}
currentCategory={group.category}
onValueChange={(value) =>
updateGroupMutation.mutate({
id: group?._id || '',
payload: { name: group?.name || '', category: value },
id: group._id || '',
payload: { name: group.name || '', category: value },
})
}
/>
@ -256,11 +260,11 @@ const PromptForm = () => {
className="h-10 border border-transparent bg-green-500 transition-all hover:bg-green-600 dark:bg-green-500 dark:hover:bg-green-600"
variant={'default'}
onClick={() => {
const { _id: promptVersionId = '', prompt } = selectedPrompt;
const { _id: promptVersionId = '', prompt } = selectedPrompt ?? ({} as TPrompt);
makeProductionMutation.mutate(
{
id: promptVersionId || '',
groupId: group?._id || '',
groupId: group._id || '',
productionPrompt: { prompt },
},
{
@ -275,7 +279,7 @@ const PromptForm = () => {
}}
disabled={
isLoadingGroup ||
selectedPrompt?._id === group?.productionId ||
selectedPrompt?._id === group.productionId ||
makeProductionMutation.isLoading
}
>
@ -288,7 +292,7 @@ const PromptForm = () => {
selectHandler={() => {
deletePromptMutation.mutate({
_id: selectedPrompt?._id || '',
groupId: group?._id || '',
groupId: group._id || '',
});
}}
/>
@ -309,11 +313,11 @@ const PromptForm = () => {
<PromptEditor name="prompt" isEditing={isEditing} setIsEditing={setIsEditing} />
<PromptVariables promptText={promptText} />
<Description
initialValue={group?.oneliner ?? ''}
initialValue={group.oneliner ?? ''}
onValueChange={debouncedUpdateOneliner}
/>
<Command
initialValue={group?.command ?? ''}
initialValue={group.command ?? ''}
onValueChange={debouncedUpdateCommand}
/>
</div>

View file

@ -1,3 +1,4 @@
import { useRecoilValue } from 'recoil';
import type { TMessageProps } from '~/common';
import MinimalHoverButtons from '~/components/Chat/Messages/MinimalHoverButtons';
import MessageContent from '~/components/Chat/Messages/Content/MessageContent';
@ -8,9 +9,11 @@ import SubRow from '~/components/Chat/Messages/SubRow';
// eslint-disable-next-line import/no-cycle
import MultiMessage from './MultiMessage';
import { cn } from '~/utils';
import store from '~/store';
import Icon from './MessageIcon';
export default function Message(props: TMessageProps) {
const fontSize = useRecoilValue(store.fontSize);
const {
message,
siblingIdx,
@ -37,8 +40,8 @@ export default function Message(props: TMessageProps) {
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">
@ -51,12 +54,12 @@ export default function Message(props: TMessageProps) {
<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">
{/* Legacy Plugins */}
{message?.plugin && <Plugin plugin={message?.plugin} />}
{message?.content ? (
{message.plugin && <Plugin plugin={message.plugin} />}
{message.content ? (
<SearchContent message={message} />
) : (
<MessageContent