🅰️ 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

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