mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 18:30:15 +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
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue