feat: Implement CreatePromptButton and enhance AutoSendPrompt with dialog settings

This commit is contained in:
Marco Beretta 2025-06-01 12:42:25 +02:00
parent 0e26df0390
commit 3e698338aa
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
9 changed files with 108 additions and 62 deletions

View file

@ -0,0 +1,34 @@
import React from 'react';
import { Plus } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
import { useLocalize, useHasAccess } from '~/hooks';
import { Button } from '~/components/ui';
const CreatePromptButton: React.FC<{ isChatRoute: boolean }> = ({ isChatRoute }) => {
const navigate = useNavigate();
const localize = useLocalize();
const hasCreateAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.CREATE,
});
return (
<>
{hasCreateAccess && (
<div className="flex w-full justify-end">
<Button
variant="outline"
className={`w-full bg-transparent ${isChatRoute ? '' : 'mx-2'}`}
onClick={() => navigate('/d/prompts/new')}
>
<Plus className="size-4" aria-hidden />
{localize('com_ui_create_prompt')}
</Button>
</div>
)}
</>
);
};
export default CreatePromptButton;

View file

@ -1,5 +1,14 @@
import { Cog } from 'lucide-react';
import { useRecoilState } from 'recoil';
import { Switch } from '~/components/ui';
import {
Label,
Switch,
Button,
OGDialog,
OGDialogTrigger,
OGDialogContent,
OGDialogTitle,
} from '~/components';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
import store from '~/store';
@ -22,20 +31,30 @@ export default function AutoSendPrompt({
};
return (
<div
className={cn(
'flex select-none items-center justify-end gap-2 text-right text-sm',
className,
)}
>
<div> {localize('com_nav_auto_send_prompts')} </div>
<Switch
aria-label="toggle-auto-send-prompts"
id="autoSendPrompts"
checked={autoSendPrompts}
onCheckedChange={handleCheckedChange}
data-testid="autoSendPrompts"
/>
</div>
<>
<OGDialog>
<OGDialogTrigger className="flex items-center justify-center">
<Button size="sm" variant="outline" className="bg-transparent hover:bg-surface-hover">
<Cog className="h-4 w-4 text-text-primary" />
</Button>
</OGDialogTrigger>
<OGDialogContent className="w-96">
<OGDialogTitle className="text-lg font-semibold">
{localize('com_ui_prompts_settings')}
</OGDialogTitle>
<div className={cn('flex justify-between text-text-secondary', className)}>
<Label>{localize('com_nav_auto_send_prompts')}</Label>
<Switch
aria-label="toggle-auto-send-prompts"
id="autoSendPrompts"
checked={autoSendPrompts}
onCheckedChange={handleCheckedChange}
data-testid="autoSendPrompts"
/>
</div>
</OGDialogContent>
</OGDialog>
</>
);
}

View file

@ -44,11 +44,11 @@ export default function FilterPrompts({
const categoryOptions = categories
? [...categories]
: [
{
value: SystemCategories.NO_CATEGORY,
label: localize('com_ui_no_category'),
},
];
{
value: SystemCategories.NO_CATEGORY,
label: localize('com_ui_no_category'),
},
];
return [...baseOptions, ...categoryOptions];
}, [categories, localize]);
@ -78,7 +78,7 @@ export default function FilterPrompts({
value={categoryFilter || SystemCategories.ALL}
onChange={onSelect}
options={filterOptions}
className="bg-transparent"
className="rounded-lg bg-transparent"
icon={<ListFilter className="h-4 w-4" />}
label="Filter: "
ariaLabel={localize('com_ui_filter_prompts')}

View file

@ -1,5 +1,3 @@
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import PanelNavigation from '~/components/Prompts/Groups/PanelNavigation';
import { useMediaQuery, usePromptGroupsNav } from '~/hooks';
import List from '~/components/Prompts/Groups/List';
@ -17,14 +15,14 @@ export default function GroupSidePanel({
groupsQuery,
promptGroups,
hasPreviousPage,
isChatRoute,
}: {
children?: React.ReactNode;
isDetailView?: boolean;
className?: string;
isChatRoute: boolean;
} & ReturnType<typeof usePromptGroupsNav>) {
const location = useLocation();
const isSmallerScreen = useMediaQuery('(max-width: 1024px)');
const isChatRoute = useMemo(() => location.pathname?.startsWith('/c/'), [location.pathname]);
return (
<div

View file

@ -1,13 +1,10 @@
import { Plus } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
import type { TPromptGroup, TStartupConfig } from 'librechat-data-provider';
import { RankablePromptList, RankingProvider } from '~/components/Prompts/Groups/RankingComponent';
import DashGroupItem from '~/components/Prompts/Groups/DashGroupItem';
import ChatGroupItem from '~/components/Prompts/Groups/ChatGroupItem';
import { useGetStartupConfig } from '~/data-provider';
import { useLocalize, useHasAccess } from '~/hooks';
import { Button, Skeleton } from '~/components/ui';
import { useLocalize } from '~/hooks';
export default function List({
groups = [],
@ -20,14 +17,9 @@ export default function List({
isLoading: boolean;
enableRanking?: boolean;
}) {
const navigate = useNavigate();
const localize = useLocalize();
const { data: startupConfig = {} as Partial<TStartupConfig> } = useGetStartupConfig();
const { instanceProjectId } = startupConfig;
const hasCreateAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.CREATE,
});
const renderGroupItem = (group: TPromptGroup) => {
if (isChatRoute) {
@ -39,18 +31,6 @@ export default function List({
return (
<RankingProvider>
<div className="flex h-full flex-col">
{hasCreateAccess && (
<div className="flex w-full justify-end">
<Button
variant="outline"
className={`w-full bg-transparent ${isChatRoute ? '' : 'mx-2'}`}
onClick={() => navigate('/d/prompts/new')}
>
<Plus className="size-4" aria-hidden />
{localize('com_ui_create_prompt')}
</Button>
</div>
)}
<div className="flex-grow overflow-y-auto">
<div className="overflow-y-auto overflow-x-hidden">
{isLoading && isChatRoute && (
@ -74,7 +54,7 @@ export default function List({
{localize('com_ui_nothing_found')}
</div>
)}
{!isLoading && groups.length > 0 && enableRanking ? (
{!isLoading && groups.length > 0 && !isChatRoute && enableRanking ? (
<RankablePromptList groups={groups} renderItem={renderGroupItem} />
) : (
!isLoading && groups.map((group) => renderGroupItem(group))

View file

@ -1,6 +1,8 @@
import { memo } from 'react';
import { Button, ThemeSelector } from '~/components/ui';
import AutoSendPrompt from '~/components/Prompts/Groups/AutoSendPrompt';
import { Button } from '~/components';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
function PanelNavigation({
prevPage,
@ -19,12 +21,16 @@ function PanelNavigation({
}) {
const localize = useLocalize();
return (
<div className="my-1 flex justify-between">
<div className={cn('my-1 flex justify-between', !isChatRoute && 'mx-2')}>
<AutoSendPrompt className="text-xs dark:text-white" />
<div className="mb-2 flex gap-2">
{!isChatRoute && <ThemeSelector returnThemeOnly={true} />}
</div>
<div className="mb-2 flex gap-2">
<Button variant="outline" size="sm" onClick={() => prevPage()} disabled={!hasPreviousPage}>
<Button
variant="outline"
size="sm"
onClick={() => prevPage()}
disabled={!hasPreviousPage}
className="bg-transparent hover:bg-surface-hover"
>
{localize('com_ui_prev')}
</Button>
<Button
@ -32,6 +38,7 @@ function PanelNavigation({
size="sm"
onClick={() => nextPage()}
disabled={!hasNextPage || isFetching}
className="bg-transparent hover:bg-surface-hover"
>
{localize('com_ui_next')}
</Button>

View file

@ -1,19 +1,24 @@
import React, { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import PromptSidePanel from '~/components/Prompts/Groups/GroupSidePanel';
import AutoSendPrompt from '~/components/Prompts/Groups/AutoSendPrompt';
import FilterPrompts from '~/components/Prompts/Groups/FilterPrompts';
import ManagePrompts from '~/components/Prompts/ManagePrompts';
import CreatePrompt from '~/components/Prompts/CreatePrompt';
import { usePromptGroupsNav } from '~/hooks';
export default function PromptsAccordion() {
const location = useLocation();
const groupsNav = usePromptGroupsNav();
const isChatRoute = useMemo(() => location.pathname?.startsWith('/c/'), [location.pathname]);
return (
<div className="flex h-full w-full flex-col">
<PromptSidePanel className="lg:w-full xl:w-full" {...groupsNav}>
<div className="flex w-full flex-row items-center justify-between pt-2">
<ManagePrompts className="select-none" />
<AutoSendPrompt className="text-xs dark:text-white" />
</div>
<div className="mt-2 flex h-full w-full flex-col">
<PromptSidePanel isChatRoute={isChatRoute} className="lg:w-full xl:w-full" {...groupsNav}>
<FilterPrompts setName={groupsNav.setName} className="items-center justify-center" />
<div className="flex w-full flex-row items-center justify-between gap-2">
<ManagePrompts className="select-none" />
<CreatePrompt isChatRoute={isChatRoute} />
</div>
</PromptSidePanel>
</div>
);

View file

@ -822,6 +822,7 @@
"com_ui_prompts_allow_create": "Allow creating Prompts",
"com_ui_prompts_allow_share_global": "Allow sharing Prompts to all users",
"com_ui_prompts_allow_use": "Allow using Prompts",
"com_ui_prompts_settings": "Prompt Settings",
"com_ui_provider": "Provider",
"com_ui_read_aloud": "Read aloud",
"com_ui_redirecting_to_provider": "Redirecting to {{0}}, please wait...",

View file

@ -4,6 +4,7 @@ import { SystemRoles } from 'librechat-data-provider';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { ArrowLeft, MessageSquareQuote } from 'lucide-react';
import {
ThemeSelector,
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
@ -14,7 +15,7 @@ import {
// DropdownMenuItem,
// DropdownMenuContent,
// DropdownMenuTrigger,
} from '~/components/ui';
} from '~/components';
import { useLocalize, useCustomLink, useAuthContext } from '~/hooks';
import AdvancedSwitch from '~/components/Prompts/AdvancedSwitch';
// import { RightPanel } from '../../components/Prompts/RightPanel';
@ -106,6 +107,7 @@ export default function DashBreadcrumb() {
</Breadcrumb>
<div className="flex items-center justify-center gap-2">
{isPromptsPath && <AdvancedSwitch />}
<ThemeSelector returnThemeOnly={true} />
{user?.role === SystemRoles.ADMIN && <AdminSettings />}
</div>
</div>