LibreChat/client/src/data-provider/prompts.ts
Marco Beretta 73fe0835cf
🎨 style: Prompt UI Refresh & A11Y Improvements (#5614)
* 🚀 feat: Add animated search input and improve filtering UI

* 🏄 refactor: Clean up category options and optimize event handlers in ChatGroupItem

* 🚀 refactor: 'Rename Prompt' option and enhance prompt filtering UI
Changed the useUpdatePromptGroup mutation in prompts.ts to replace the JSON.parse(JSON.stringify(...)) clones with structuredClone. This avoids errors when data contains non‑JSON values and improves data cloning reliability

* 🔧 refactor: Update Sharing Prompts UI; fix: Show info message only after updating switch status

* 🔧 refactor: Simplify condition checks and replace button with custom Button component in SharePrompt

* 🔧 refactor: Update DashGroupItem styles and improve accessibility with updated aria-label

* 🔧 refactor: Adjust layout styles in GroupSidePanel and enhance loading skeletons in List component

* 🔧 refactor: Improve layout and styling of AdvancedSwitch component; adjust DashBreadcrumb margin for better alignment

* 🔧 refactor: Add new surface colors for destructive actions and update localization strings for confirmation prompts

* 🔧 refactor: Update PromptForm and PromptName components for improved layout and styling; replace button with custom Button component

* 🔧 refactor: Enhance styling and layout of DashGroupItem, FilterPrompts, and Label components for improved user experience

* 🔧 refactor: Update DeleteBookmarkButton and Label components for improved layout and text handling

* 🔧 refactor: Simplify CategorySelector usage and update destructive surface colors for a11y

* 🔧 refactor: Update styling and layout of PromptName, SharePrompt, and DashGroupItem components; enhance Dropdown functionality with custom renderValue

* 🔧 refactor: Improve layout and styling of various components; update button sizes and localization strings for better accessibility and user experience

* 🔧 refactor: Add useCurrentPromptData hook and enhance RightPanel component; update CategorySelector for improved functionality and accessibility

* 🔧 refactor: Update input components and styling for Command and Description; enhance layout and accessibility in PromptVariables and PromptForm

* 🔧 refactor: Remove useCurrentPromptData hook and clean up related components; enhance PromptVersions layout

* 🔧 refactor: Enhance accessibility by adding aria-labels to buttons and inputs; improve localization for filter prompts

* 🔧 refactor: Enhance accessibility by adding aria-labels to various components; improve layout and styling in PromptForm and CategorySelector

* 🔧 refactor: Enhance accessibility by adding aria-labels to buttons and components; improve dialog roles and descriptions in SharePrompt and PromptForm

* 🔧 refactor: Improve accessibility by adding aria-labels and roles; enhance layout and styling in ChatGroupItem, ListCard, and ManagePrompts components

* 🔧 refactor: Update UI components for improved styling and accessibility; replace button elements with custom Button component and enhance layout in VariableForm, PromptDetails, and PromptVariables

* 🔧 refactor: Improve null checks for group and instanceProjectId in SharePrompt component; enhance readability and maintainability

* style: Enhance AnimatedSearchInput component with TypeScript types; improve conditional rendering for search states and accessibility

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2025-02-05 11:37:17 -05:00

329 lines
10 KiB
TypeScript

import { useRecoilValue } from 'recoil';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { dataService, QueryKeys } from 'librechat-data-provider';
import type { UseMutationResult } from '@tanstack/react-query';
import type t from 'librechat-data-provider';
import {
/* Prompts */
addGroupToAll,
addPromptGroup,
updateGroupInAll,
updateGroupFields,
deletePromptGroup,
removeGroupFromAll,
} from '~/utils';
import store from '~/store';
export const useUpdatePromptGroup = (
options?: t.UpdatePromptGroupOptions,
): UseMutationResult<
t.TUpdatePromptGroupResponse,
unknown,
t.TUpdatePromptGroupVariables,
unknown
> => {
const { onMutate, onError, onSuccess } = options || {};
const queryClient = useQueryClient();
const name = useRecoilValue(store.promptsName);
const pageSize = useRecoilValue(store.promptsPageSize);
const category = useRecoilValue(store.promptsCategory);
return useMutation({
mutationFn: (variables: t.TUpdatePromptGroupVariables) =>
dataService.updatePromptGroup(variables),
onMutate: (variables: t.TUpdatePromptGroupVariables) => {
const groupData = queryClient.getQueryData<t.TPromptGroup>([
QueryKeys.promptGroup,
variables.id,
]);
const group = groupData ? structuredClone(groupData) : undefined;
const groupListData = queryClient.getQueryData<t.PromptGroupListData>([
QueryKeys.promptGroups,
name,
category,
pageSize,
]);
const previousListData = groupListData ? structuredClone(groupListData) : undefined;
let update = variables.payload;
if (update.removeProjectIds && group?.projectIds) {
update = structuredClone(update);
update.projectIds = group.projectIds.filter((id) => !update.removeProjectIds?.includes(id));
delete update.removeProjectIds;
}
if (groupListData) {
const newData = updateGroupFields(
/* Paginated Data */
groupListData,
/* Update */
{ _id: variables.id, ...update },
/* Callback */
(group) => queryClient.setQueryData([QueryKeys.promptGroup, variables.id], group),
);
queryClient.setQueryData<t.PromptGroupListData>(
[QueryKeys.promptGroups, name, category, pageSize],
newData,
);
}
if (onMutate) {
onMutate(variables);
}
return { group, previousListData };
},
onError: (err, variables, context) => {
if (context?.group) {
queryClient.setQueryData([QueryKeys.promptGroups, variables.id], context.group);
}
if (context?.previousListData) {
queryClient.setQueryData<t.PromptGroupListData>(
[QueryKeys.promptGroups, name, category, pageSize],
context.previousListData,
);
}
if (onError) {
onError(err, variables, context);
}
},
onSuccess: (response, variables, context) => {
updateGroupInAll(queryClient, { _id: variables.id, ...response });
if (onSuccess) {
onSuccess(response, variables, context);
}
},
});
};
export const useCreatePrompt = (
options?: t.CreatePromptOptions,
): UseMutationResult<t.TCreatePromptResponse, unknown, t.TCreatePrompt, unknown> => {
const queryClient = useQueryClient();
const { onSuccess, ...rest } = options || {};
const name = useRecoilValue(store.promptsName);
const pageSize = useRecoilValue(store.promptsPageSize);
const category = useRecoilValue(store.promptsCategory);
return useMutation({
mutationFn: (payload: t.TCreatePrompt) => dataService.createPrompt(payload),
...rest,
onSuccess: (response, variables, context) => {
const { prompt, group } = response;
queryClient.setQueryData(
[QueryKeys.prompts, variables.prompt.groupId],
(oldData: t.TPrompt[] | undefined) => {
return [prompt, ...(oldData ?? [])];
},
);
if (group) {
queryClient.setQueryData<t.PromptGroupListData>(
[QueryKeys.promptGroups, name, category, pageSize],
(data) => {
if (!data) {
return data;
}
return addPromptGroup(data, group);
},
);
addGroupToAll(queryClient, group);
}
if (onSuccess) {
onSuccess(response, variables, context);
}
},
});
};
export const useDeletePrompt = (
options?: t.DeletePromptOptions,
): UseMutationResult<t.TDeletePromptResponse, unknown, t.TDeletePromptVariables, unknown> => {
const queryClient = useQueryClient();
const { onSuccess, ...rest } = options || {};
const name = useRecoilValue(store.promptsName);
const pageSize = useRecoilValue(store.promptsPageSize);
const category = useRecoilValue(store.promptsCategory);
return useMutation({
mutationFn: (payload: t.TDeletePromptVariables) => dataService.deletePrompt(payload),
...rest,
onSuccess: (response, variables, context) => {
if (response.promptGroup) {
const promptGroupId = response.promptGroup.id;
queryClient.setQueryData<t.PromptGroupListData>(
[QueryKeys.promptGroups, name, category, pageSize],
(data) => {
if (!data) {
return data;
}
return deletePromptGroup(data, promptGroupId);
},
);
removeGroupFromAll(queryClient, promptGroupId);
} else {
queryClient.setQueryData<t.TPrompt[]>(
[QueryKeys.prompts, variables.groupId],
(oldData?: t.TPrompt[]) => {
const prompts = oldData ? oldData.filter((prompt) => prompt._id !== variables._id) : [];
queryClient.setQueryData<t.TPromptGroup>(
[QueryKeys.promptGroup, variables.groupId],
(data) => {
if (!data) {
return data;
}
if (data.productionId === variables._id) {
data.productionId = prompts[0]._id;
data.productionPrompt = prompts[0];
}
},
);
return prompts;
},
);
}
if (onSuccess) {
onSuccess(response, variables, context);
}
},
});
};
export const useDeletePromptGroup = (
options?: t.DeletePromptGroupOptions,
): UseMutationResult<
t.TDeletePromptGroupResponse,
unknown,
t.TDeletePromptGroupRequest,
unknown
> => {
const queryClient = useQueryClient();
const { onSuccess, ...rest } = options || {};
const name = useRecoilValue(store.promptsName);
const pageSize = useRecoilValue(store.promptsPageSize);
const category = useRecoilValue(store.promptsCategory);
return useMutation({
mutationFn: (variables: t.TDeletePromptGroupRequest) =>
dataService.deletePromptGroup(variables.id),
...rest,
onSuccess: (response, variables, context) => {
queryClient.setQueryData<t.PromptGroupListData>(
[QueryKeys.promptGroups, name, category, pageSize],
(data) => {
if (!data) {
return data;
}
return deletePromptGroup(data, variables.id);
},
);
removeGroupFromAll(queryClient, variables.id);
if (onSuccess) {
onSuccess(response, variables, context);
}
},
});
};
export const useUpdatePromptLabels = (
options?: t.UpdatePromptLabelOptions,
): UseMutationResult<
t.TUpdatePromptLabelsResponse,
unknown,
t.TUpdatePromptLabelsRequest,
unknown
> => {
const { onSuccess, ...rest } = options || {};
return useMutation({
mutationFn: (variables: t.TUpdatePromptLabelsRequest) =>
dataService.updatePromptLabels(variables),
...rest,
onSuccess: (response, variables, context) => {
if (onSuccess) {
onSuccess(response, variables, context);
}
},
});
};
export const useMakePromptProduction = (options?: t.MakePromptProductionOptions) => {
const queryClient = useQueryClient();
const { onSuccess, onError, onMutate } = options || {};
const name = useRecoilValue(store.promptsName);
const pageSize = useRecoilValue(store.promptsPageSize);
const category = useRecoilValue(store.promptsCategory);
return useMutation({
mutationFn: (variables: t.TMakePromptProductionRequest) =>
dataService.makePromptProduction(variables.id),
onMutate: (variables: t.TMakePromptProductionRequest) => {
const group = JSON.parse(
JSON.stringify(
queryClient.getQueryData<t.TPromptGroup>([QueryKeys.promptGroup, variables.groupId]),
),
) as t.TPromptGroup;
const groupData = queryClient.getQueryData<t.PromptGroupListData>([
QueryKeys.promptGroups,
name,
category,
pageSize,
]);
const previousListData = JSON.parse(JSON.stringify(groupData)) as t.PromptGroupListData;
if (groupData) {
const newData = updateGroupFields(
/* Paginated Data */
groupData,
/* Update */
{
_id: variables.groupId,
productionId: variables.id,
productionPrompt: variables.productionPrompt,
},
/* Callback */
(group) => queryClient.setQueryData([QueryKeys.promptGroup, variables.groupId], group),
);
queryClient.setQueryData<t.PromptGroupListData>(
[QueryKeys.promptGroups, name, category, pageSize],
newData,
);
}
if (onMutate) {
onMutate(variables);
}
return { group, previousListData };
},
onError: (err, variables, context) => {
if (context?.group) {
queryClient.setQueryData([QueryKeys.promptGroups, variables.groupId], context.group);
}
if (context?.previousListData) {
queryClient.setQueryData<t.PromptGroupListData>(
[QueryKeys.promptGroups, name, category, pageSize],
context.previousListData,
);
}
if (onError) {
onError(err, variables, context);
}
},
onSuccess: (response, variables, context) => {
updateGroupInAll(queryClient, {
_id: variables.groupId,
productionId: variables.id,
productionPrompt: variables.productionPrompt,
});
if (onSuccess) {
onSuccess(response, variables, context);
}
},
});
};