💬 fix: Temporary Chat PR's broken components and improved UI (#5705)

* 💬 fix: Temporary Chat PR's broken components and improved UI

* 💬 fix: bring back hover effect on AudioRecorder button

* style: adjust position of Mention component popover

* refactor: PromptsCommand typing and style position

* refactor: virtualize mention UI

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2025-02-07 02:15:38 +01:00 committed by GitHub
parent 63afb317c6
commit 70e410f38b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 196 additions and 86 deletions

View file

@ -1,4 +1,5 @@
import { useState, useRef, useEffect, useMemo, memo, useCallback } from 'react';
import { AutoSizer, List } from 'react-virtualized';
import { useSetRecoilState, useRecoilValue } from 'recoil';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
import type { TPromptGroup } from 'librechat-data-provider';
@ -42,6 +43,8 @@ const PopoverContainer = memo(
},
);
const ROW_HEIGHT = 40;
function PromptsCommand({
index,
textAreaRef,
@ -63,8 +66,10 @@ function PromptsCommand({
const mappedArray = data.map((group) => ({
id: group._id,
value: group.command ?? group.name,
label: `${group.command ? `/${group.command} - ` : ''}${group.name}: ${
group.oneliner?.length ? group.oneliner : group.productionPrompt?.prompt ?? ''
label: `${group.command != null && group.command ? `/${group.command} - ` : ''}${
group.name
}: ${
(group.oneliner?.length ?? 0) > 0 ? group.oneliner : group.productionPrompt?.prompt ?? ''
}`,
icon: <CategoryIcon category={group.category ?? ''} className="h-5 w-5" />,
}));
@ -85,12 +90,12 @@ function PromptsCommand({
const [variableGroup, setVariableGroup] = useState<TPromptGroup | null>(null);
const setShowPromptsPopover = useSetRecoilState(store.showPromptsPopoverFamily(index));
const prompts = useMemo(() => data?.promptGroups ?? [], [data]);
const promptsMap = useMemo(() => data?.promptsMap ?? {}, [data]);
const prompts = useMemo(() => data?.promptGroups, [data]);
const promptsMap = useMemo(() => data?.promptsMap, [data]);
const { open, setOpen, searchValue, setSearchValue, matches } = useCombobox({
value: '',
options: prompts,
options: prompts ?? [],
});
const handleSelect = useCallback(
@ -107,22 +112,20 @@ function PromptsCommand({
removeCharIfLast(textAreaRef.current, commandChar);
}
const isValidPrompt = mention && promptsMap && promptsMap[mention.id];
if (!isValidPrompt) {
const group = promptsMap?.[mention.id];
if (!group) {
return;
}
const group = promptsMap[mention.id];
const hasVariables = detectVariables(group.productionPrompt?.prompt ?? '');
if (group && hasVariables) {
if (hasVariables) {
if (e && e.key === 'Tab') {
e.preventDefault();
}
setVariableGroup(group);
setVariableDialogOpen(true);
return;
} else if (group) {
} else {
submitPrompt(group.productionPrompt?.prompt ?? '');
}
},
@ -154,6 +157,37 @@ function PromptsCommand({
return null;
}
const rowRenderer = ({
index,
key,
style,
}: {
index: number;
key: string;
style: React.CSSProperties;
}) => {
const mention = matches[index] as PromptOption;
return (
<MentionItem
index={index}
type="prompt"
key={key}
style={style}
onClick={() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = null;
handleSelect(mention);
}}
name={mention.label ?? ''}
icon={mention.icon}
description={mention.description}
isActive={index === activeIndex}
/>
);
};
return (
<PopoverContainer
index={index}
@ -161,7 +195,7 @@ function PromptsCommand({
variableGroup={variableGroup}
setVariableDialogOpen={setVariableDialogOpen}
>
<div className="absolute bottom-16 z-10 w-full space-y-2">
<div className="absolute bottom-14 z-10 w-full space-y-2">
<div className="popover border-token-border-light rounded-2xl border bg-surface-tertiary-alt p-2 shadow-lg">
<input
// The user expects focus to transition to the input field when the popover is opened
@ -213,24 +247,23 @@ function PromptsCommand({
}
if (!isLoading && open) {
return (matches as PromptOption[]).map((mention, index) => (
<MentionItem
index={index}
type="prompt"
key={`${mention.value}-${index}`}
onClick={() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = null;
handleSelect(mention);
}}
name={mention.label ?? ''}
icon={mention.icon}
description={mention.description}
isActive={index === activeIndex}
/>
));
return (
<div className="max-h-40">
<AutoSizer disableHeight>
{({ width }) => (
<List
width={width}
overscanRowCount={5}
rowHeight={ROW_HEIGHT}
rowCount={matches.length}
rowRenderer={rowRenderer}
scrollToIndex={activeIndex}
height={Math.min(matches.length * ROW_HEIGHT, 160)}
/>
)}
</AutoSizer>
</div>
);
}
return null;
})()}