Skip to content

Commit

Permalink
🔧 refactor: Add useCurrentPromptData hook and enhance RightPanel comp…
Browse files Browse the repository at this point in the history
…onent; update CategorySelector for improved functionality and accessibility
  • Loading branch information
berry-13 committed Feb 2, 2025
1 parent ea73fe6 commit f642957
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 97 deletions.
35 changes: 27 additions & 8 deletions client/src/components/Prompts/Groups/CategorySelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ import { LocalStorageKeys } from 'librechat-data-provider';
import { Dropdown } from '~/components/ui';
import { useCategories } from '~/hooks';

const CategorySelector = ({
currentCategory,
onValueChange,
}: {
interface CategorySelectorProps {
currentCategory?: string;
onValueChange?: (value: string) => void;
}) => {
const { control, watch, setValue } = useFormContext();
}

const CategorySelector: React.FC<CategorySelectorProps> = ({ currentCategory, onValueChange }) => {
const formContext = useFormContext();
const { categories, emptyCategory } = useCategories();

const watchedCategory = watch('category');
const control = formContext.control;
const watch = formContext.watch;
const setValue = formContext.setValue;

const watchedCategory = watch ? watch('category') : currentCategory;

const categoryOption = useMemo(
() =>
(categories ?? []).find(
Expand All @@ -23,7 +27,7 @@ const CategorySelector = ({
[watchedCategory, categories, currentCategory, emptyCategory],
);

return (
return formContext ? (
<Controller
name="category"
control={control}
Expand All @@ -46,6 +50,21 @@ const CategorySelector = ({
/>
)}
/>
) : (
<Dropdown
value={currentCategory ?? ''}
onChange={(value: string) => {
localStorage.setItem(LocalStorageKeys.LAST_PROMPT_CATEGORY, value);
onValueChange?.(value);
}}
options={categories || []}
renderValue={(option) => (
<div className="flex items-center space-x-2">
{option.icon != null && <span>{option.icon as React.ReactNode}</span>}
<span>{option.label}</span>
</div>
)}
/>
);
};

Expand Down
104 changes: 17 additions & 87 deletions client/src/components/Prompts/PromptForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Menu } from 'lucide-react';
import debounce from 'lodash/debounce';
import { useRecoilValue } from 'recoil';
import { Rocket, Menu } from 'lucide-react';
import { useForm, FormProvider } from 'react-hook-form';
import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
import { useNavigate, useParams, useOutletContext } from 'react-router-dom';
Expand All @@ -15,20 +15,17 @@ import {
useMakePromptProduction,
} from '~/data-provider';
import { useAuthContext, usePromptGroupsNav, useHasAccess, useLocalize } from '~/hooks';
import CategorySelector from './Groups/CategorySelector';
import NoPromptGroup from './Groups/NoPromptGroup';
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 { RightPanel } from './RightPanel';
import { findPromptGroup } from '~/utils';
import PromptEditor from './PromptEditor';
import SkeletonForm from './SkeletonForm';
import Description from './Description';
import SharePrompt from './SharePrompt';
import PromptName from './PromptName';
import Command from './Command';
import { cn } from '~/utils';
Expand Down Expand Up @@ -237,82 +234,6 @@ const PromptForm = () => {
return null;
}

const renderRightPanel = () => (
<div className="w-full overflow-y-auto px-4" style={{ maxHeight: 'calc(100vh - 150px)' }}>
<div className="mb-2 flex flex-row items-center justify-center gap-x-2 lg:flex-col lg:space-y-2 xl:flex-row xl:space-y-0">
<CategorySelector
currentCategory={group.category}
onValueChange={(value) =>
updateGroupMutation.mutate({
id: group._id || '',
payload: { name: group.name || '', category: value },
})
}
/>
<div className="flex flex-row items-center justify-center gap-x-2">
{hasShareAccess && <SharePrompt group={group} disabled={isLoadingGroup} />}
{editorMode === PromptsEditorMode.ADVANCED && (
<Button
variant="submit"
size="sm"
className="h-10 w-10 border border-transparent p-0.5 transition-all"
onClick={() => {
const { _id: promptVersionId = '', prompt } = selectedPrompt ?? ({} as TPrompt);
makeProductionMutation.mutate(
{
id: promptVersionId || '',
groupId: group._id || '',
productionPrompt: { prompt },
},
{
onSuccess: (_data, variables) => {
const productionIndex = prompts.findIndex(
(prompt) => variables.id === prompt._id,
);
setSelectionIndex(productionIndex);
},
},
);
}}
disabled={
isLoadingGroup ||
selectedPrompt?._id === group.productionId ||
makeProductionMutation.isLoading
}
>
<Rocket className="size-5 cursor-pointer text-white" />
</Button>
)}
<DeleteConfirm
name={group.name}
disabled={isLoadingGroup}
selectHandler={() => {
deletePromptMutation.mutate({
_id: selectedPrompt?._id || '',
groupId: group._id || '',
});
}}
/>
</div>
</div>
{editorMode === PromptsEditorMode.ADVANCED &&
(isLoadingPrompts
? Array.from({ length: 6 }).map((_: unknown, index: number) => (
<div key={index} className="my-2">
<Skeleton className="h-[72px] w-full" />
</div>
))
: !!prompts.length && (
<PromptVersions
group={group}
prompts={prompts}
selectionIndex={selectionIndex}
setSelectionIndex={setSelectionIndex}
/>
))}
</div>
);

return (
<FormProvider {...methods}>
<form className="mt-4 h-full w-full" onSubmit={handleSubmit((data) => onSave(data.prompt))}>
Expand All @@ -329,7 +250,7 @@ const PromptForm = () => {
<div className="flex h-full flex-row">
{/* Left Panel */}
<div className="flex-1 px-4">
<div className="mb-4 flex items-center gap-2 text-text-primary">
<div className="my-4 flex items-center gap-2 text-text-primary">
{isLoadingGroup ? (
<Skeleton className="mb-1 flex h-10 w-32 font-bold sm:text-xl md:mb-0 md:h-12 md:text-2xl" />
) : (
Expand All @@ -354,6 +275,10 @@ const PromptForm = () => {
>
<Menu className="size-5" />
</Button>
<div className="flex-1" />
<div className="hidden lg:block">
{editorMode === PromptsEditorMode.SIMPLE && <RightPanel />}
</div>
</>
)}
</div>
Expand All @@ -379,10 +304,12 @@ const PromptForm = () => {
<div className="hidden border-l border-border-light lg:block" />

{/* Desktop Right Panel */}
<div className="hidden w-1/4 lg:block">
<div className="border-l border-border-light" />
{renderRightPanel()}
</div>
{editorMode === PromptsEditorMode.ADVANCED && (
<div className="hidden w-1/4 lg:block">
<div className="border-l border-border-light" />
<RightPanel />
</div>
)}
</div>
</div>

Expand All @@ -404,6 +331,7 @@ const PromptForm = () => {
/>

{/* Mobile Right Side Panel (pushes the main content left) */}

<div
className="absolute inset-y-0 right-0 z-50 flex shadow-lg will-change-transform lg:hidden"
style={{
Expand All @@ -414,7 +342,9 @@ const PromptForm = () => {
}}
>
<div className="flex h-full w-full flex-col">
<div className="mt-4 flex-1 overflow-auto">{renderRightPanel()}</div>
<div className="mt-4 flex-1 overflow-auto">
<RightPanel />
</div>
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions client/src/components/Prompts/PromptVersions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const VersionCard = ({

return (
<button
type="button"
className={cn(
'group relative w-full rounded-lg border border-border-light p-4 transition-all duration-300',
isSelected
Expand Down
112 changes: 112 additions & 0 deletions client/src/components/Prompts/RightPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Rocket } from 'lucide-react';
import { TPrompt } from 'librechat-data-provider';
import CategorySelector from './Groups/CategorySelector';
import { Button, Skeleton } from '~/components/ui';
import { useCurrentPromptData } from '~/hooks';
import PromptVersions from './PromptVersions';
import { PromptsEditorMode } from '~/common';
import DeleteConfirm from './DeleteVersion';
import SharePrompt from './SharePrompt';

export const RightPanel = () => {
const {
group,
prompts,
selectedPrompt,
selectionIndex,
setSelectionIndex,
isLoadingGroup,
isLoadingPrompts,
hasShareAccess,
editorMode,
updateGroupMutation,
makeProductionMutation,
deletePromptMutation,
} = useCurrentPromptData();

if (!group || typeof group !== 'object') {
return null;
}

const groupId = group._id || '';
const groupName = group.name || '';
const groupCategory = group.category || '';

return (
<div
className="h-full w-full overflow-y-auto px-4"
style={{ maxHeight: 'calc(100vh - 100px)' }}
>
<div className="mb-2 flex flex-row items-center justify-center gap-x-2 lg:flex-col lg:space-y-2 xl:flex-row xl:space-y-0">
<CategorySelector
currentCategory={groupCategory}
onValueChange={(value) =>
updateGroupMutation.mutate({
id: groupId,
payload: { name: groupName, category: value },
})
}
/>
<div className="flex flex-row items-center justify-center gap-x-2">
{hasShareAccess && <SharePrompt group={group} disabled={isLoadingGroup} />}
{editorMode === PromptsEditorMode.ADVANCED && (
<Button
variant="submit"
size="sm"
className="h-10 w-10 border border-transparent p-0.5 transition-all"
onClick={() => {
if (!selectedPrompt) {
console.warn('No prompt is selected');
return;
}
const { _id: promptVersionId = '', prompt } = selectedPrompt;
makeProductionMutation.mutate(
{ id: promptVersionId, groupId, productionPrompt: { prompt } },
{
onSuccess: (_data, variables) => {
const productionIndex = prompts.findIndex((p) => variables.id === p._id);
setSelectionIndex(productionIndex);
},
},
);
}}
disabled={
isLoadingGroup ||
!selectedPrompt ||
selectedPrompt._id === group.productionId ||
makeProductionMutation.isLoading
}
>
<Rocket className="size-5 cursor-pointer text-white" />
</Button>
)}
<DeleteConfirm
name={groupName}
disabled={isLoadingGroup}
selectHandler={() => {
deletePromptMutation.mutate({
_id: selectedPrompt._id || '',
groupId,
});
}}
/>
</div>
</div>
{editorMode === PromptsEditorMode.ADVANCED &&
(isLoadingPrompts
? Array.from({ length: 6 }).map((_: unknown, index: number) => (
<div key={index} className="my-2">
<Skeleton className="h-[72px] w-full" />
</div>
))
: !!prompts.length && (
<PromptVersions
group={group}
prompts={prompts}
selectionIndex={selectionIndex}
setSelectionIndex={setSelectionIndex}
/>
))}
</div>
);
};
3 changes: 2 additions & 1 deletion client/src/components/ui/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const Dropdown: React.FC<DropdownProps> = ({
store={selectProps}
className={cn(
'focus:ring-offset-ring-offset relative inline-flex items-center justify-between rounded-lg border border-input bg-background px-3 py-2 text-sm text-text-primary transition-all duration-200 ease-in-out hover:bg-accent hover:text-accent-foreground focus:ring-ring-primary',
iconOnly ? 'h-full w-10' : 'h-10 w-fit gap-2',
iconOnly ? 'h-full w-10' : 'w-fit gap-2', // removed fixed height 'h-10'
className,
)}
data-testid={testId}
Expand All @@ -90,6 +90,7 @@ const Dropdown: React.FC<DropdownProps> = ({
{!iconOnly && <Select.SelectArrow />}
</Select.Select>
<Select.SelectPopover
portal
store={selectProps}
className={cn('popover-ui', sizeClasses, className)}
>
Expand Down
1 change: 1 addition & 0 deletions client/src/hooks/Prompts/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as useCategories } from './useCategories';
export { default as usePromptGroupsNav } from './usePromptGroupsNav';
export { useCurrentPromptData } from './useCurrentPromptData';
Loading

0 comments on commit f642957

Please sign in to comment.