mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-02 08:38:51 +01:00
✨ feat: Implement search parameter updates and enhance chat component styles
- Added `useUpdateSearchParams` hook to manage URL search parameters dynamically. - Updated `Landing`, `NewChat`, and various parameter components to utilize the new hook for better state management. - Enhanced styling for dark mode support in chat components. - Refactored click handlers in `NewChat` to create chat search parameters for navigation. - Improved accessibility and usability in `DynamicCombobox`, `DynamicInput`, `DynamicSlider`, `DynamicSwitch`, and `DynamicTags` components. - Introduced `createChatSearchParams` utility to streamline query parameter creation for chat sessions. remove icon borders in dark mode
This commit is contained in:
parent
c0ebb434a6
commit
cd4a3bd061
16 changed files with 599 additions and 74 deletions
|
|
@ -9,7 +9,7 @@ import { useLocalize, useAuthContext } from '~/hooks';
|
|||
import { getIconEndpoint, getEntity } from '~/utils';
|
||||
|
||||
const containerClassName =
|
||||
'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white text-black';
|
||||
'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white dark:bg-presentation dark:text-white text-black dark:after:shadow-none ';
|
||||
|
||||
function getTextSizeClass(text: string | undefined | null) {
|
||||
if (!text) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom';
|
|||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys, Constants } from 'librechat-data-provider';
|
||||
import type { TConversation, TMessage } from 'librechat-data-provider';
|
||||
import { getEndpointField, getIconEndpoint, getIconKey } from '~/utils';
|
||||
import { createChatSearchParams, getEndpointField, getIconEndpoint, getIconKey } from '~/utils';
|
||||
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import { useLocalize, useNewConvo } from '~/hooks';
|
||||
|
|
@ -57,7 +57,7 @@ const NewChatButtonIcon = React.memo(({ conversation }: { conversation: TConvers
|
|||
context="nav"
|
||||
/>
|
||||
) : (
|
||||
<div className="shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white text-black">
|
||||
<div className="shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white text-black dark:bg-surface-primary-alt dark:text-white dark:after:shadow-none">
|
||||
{endpoint && Icon && (
|
||||
<Icon
|
||||
size={41}
|
||||
|
|
@ -90,33 +90,29 @@ export default function NewChat({
|
|||
const navigate = useNavigate();
|
||||
const localize = useLocalize();
|
||||
const { conversation } = store.useCreateConversationAtom(index);
|
||||
const defaultPreset = useRecoilValue(store.defaultPreset);
|
||||
|
||||
const clickHandler = useCallback(
|
||||
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
if (event.button === 0 && !(event.ctrlKey || event.metaKey)) {
|
||||
event.preventDefault();
|
||||
queryClient.setQueryData<TMessage[]>(
|
||||
[QueryKeys.messages, conversation?.conversationId ?? Constants.NEW_CONVO],
|
||||
[],
|
||||
);
|
||||
newConvo();
|
||||
navigate('/c/new');
|
||||
toggleNav();
|
||||
}
|
||||
},
|
||||
[queryClient, conversation, newConvo, navigate, toggleNav],
|
||||
);
|
||||
const clickHandler = useCallback(() => {
|
||||
queryClient.setQueryData<TMessage[]>(
|
||||
[QueryKeys.messages, conversation?.conversationId ?? Constants.NEW_CONVO],
|
||||
[],
|
||||
);
|
||||
const params = createChatSearchParams(defaultPreset ?? conversation);
|
||||
const newRoute = params.size > 0 ? `/c/new?${params.toString()}` : '/c/new';
|
||||
|
||||
newConvo();
|
||||
navigate(newRoute);
|
||||
toggleNav();
|
||||
}, [queryClient, conversation, newConvo, navigate, toggleNav, defaultPreset]);
|
||||
|
||||
return (
|
||||
<div className="sticky left-0 right-0 top-0 z-50 bg-surface-primary-alt pt-3.5">
|
||||
<div className="pb-0.5 last:pb-0" style={{ transform: 'none' }}>
|
||||
<a
|
||||
href="/"
|
||||
tabIndex={0}
|
||||
<button
|
||||
data-testid="nav-new-chat-button"
|
||||
onClick={clickHandler}
|
||||
className={cn(
|
||||
'group flex h-10 items-center gap-2 rounded-lg px-2 font-medium transition-colors duration-200 hover:bg-surface-hover',
|
||||
'group flex h-10 w-full items-center gap-2 rounded-lg px-2 font-medium transition-colors duration-200 hover:bg-surface-hover',
|
||||
isSmallScreen ? 'h-14' : '',
|
||||
)}
|
||||
aria-label={localize('com_ui_new_chat')}
|
||||
|
|
@ -130,7 +126,7 @@ export default function NewChat({
|
|||
<NewChatIcon className="size-5" />
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
{subHeaders != null ? subHeaders : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { OptionTypes } from 'librechat-data-provider';
|
|||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import ControlCombobox from '~/components/ui/ControlCombobox';
|
||||
import { TranslationKeys, useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { TranslationKeys, useLocalize, useParameterEffects, useUpdateSearchParams } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
|
|
@ -33,6 +33,7 @@ function DynamicCombobox({
|
|||
}: DynamicSettingProps & { isCollapsed?: boolean; SelectIcon?: React.ReactNode }) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
const [inputValue, setInputValue] = useState<string | null>(null);
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
|
|
@ -59,8 +60,10 @@ function DynamicCombobox({
|
|||
} else {
|
||||
setOption(settingKey)(value);
|
||||
}
|
||||
|
||||
updateSearchParams({ [settingKey]: value });
|
||||
},
|
||||
[optionType, setOption, settingKey],
|
||||
[optionType, setOption, settingKey, updateSearchParams],
|
||||
);
|
||||
|
||||
useParameterEffects({
|
||||
|
|
@ -93,7 +96,7 @@ function DynamicCombobox({
|
|||
htmlFor={`${settingKey}-dynamic-combobox`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label as TranslationKeys) ?? label : label || settingKey}
|
||||
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue})
|
||||
|
|
@ -105,10 +108,14 @@ function DynamicCombobox({
|
|||
<ControlCombobox
|
||||
displayValue={selectedValue}
|
||||
selectPlaceholder={
|
||||
selectPlaceholderCode === true ? localize(selectPlaceholder as TranslationKeys) : selectPlaceholder
|
||||
selectPlaceholderCode === true
|
||||
? localize(selectPlaceholder as TranslationKeys)
|
||||
: selectPlaceholder
|
||||
}
|
||||
searchPlaceholder={
|
||||
searchPlaceholderCode === true ? localize(searchPlaceholder as TranslationKeys) : searchPlaceholder
|
||||
searchPlaceholderCode === true
|
||||
? localize(searchPlaceholder as TranslationKeys)
|
||||
: searchPlaceholder
|
||||
}
|
||||
isCollapsed={isCollapsed}
|
||||
ariaLabel={settingKey}
|
||||
|
|
@ -120,7 +127,11 @@ function DynamicCombobox({
|
|||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description as TranslationKeys) ?? description : description}
|
||||
description={
|
||||
descriptionCode
|
||||
? (localize(description as TranslationKeys) ?? description)
|
||||
: description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { useLocalize, useDebouncedInput, useParameterEffects, TranslationKeys } from '~/hooks';
|
||||
import {
|
||||
useLocalize,
|
||||
useDebouncedInput,
|
||||
useParameterEffects,
|
||||
TranslationKeys,
|
||||
useUpdateSearchParams,
|
||||
} from '~/hooks';
|
||||
import { Label, Input, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
|
|
@ -26,6 +32,7 @@ function DynamicInput({
|
|||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
|
||||
const [setInputValue, inputValue, setLocalValue] = useDebouncedInput<string | number>({
|
||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||
|
|
@ -47,6 +54,7 @@ function DynamicInput({
|
|||
const value = e.target.value;
|
||||
if (type !== 'number') {
|
||||
setInputValue(e);
|
||||
updateSearchParams({ [settingKey]: value });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +63,7 @@ function DynamicInput({
|
|||
} else if (!isNaN(Number(value))) {
|
||||
setInputValue(e, true);
|
||||
}
|
||||
updateSearchParams({ [settingKey]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ import { useMemo, useCallback } from 'react';
|
|||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, Slider, HoverCard, Input, InputNumber, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useDebouncedInput, useParameterEffects, TranslationKeys } from '~/hooks';
|
||||
import {
|
||||
useLocalize,
|
||||
useDebouncedInput,
|
||||
useParameterEffects,
|
||||
TranslationKeys,
|
||||
useUpdateSearchParams,
|
||||
} from '~/hooks';
|
||||
import { cn, defaultTextProps, optionText } from '~/utils';
|
||||
import { ESide, defaultDebouncedDelay } from '~/common';
|
||||
import { useChatContext } from '~/Providers';
|
||||
|
|
@ -31,6 +37,7 @@ function DynamicSlider({
|
|||
() => (!range && options && options.length > 0) ?? false,
|
||||
[options, range],
|
||||
);
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
|
||||
const [setInputValue, inputValue, setLocalValue] = useDebouncedInput<string | number>({
|
||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||
|
|
@ -60,20 +67,26 @@ function DynamicSlider({
|
|||
|
||||
const enumToNumeric = useMemo(() => {
|
||||
if (isEnum && options) {
|
||||
return options.reduce((acc, mapping, index) => {
|
||||
acc[mapping] = index;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
return options.reduce(
|
||||
(acc, mapping, index) => {
|
||||
acc[mapping] = index;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}, [isEnum, options]);
|
||||
|
||||
const valueToEnumOption = useMemo(() => {
|
||||
if (isEnum && options) {
|
||||
return options.reduce((acc, option, index) => {
|
||||
acc[index] = option;
|
||||
return acc;
|
||||
}, {} as Record<number, string>);
|
||||
return options.reduce(
|
||||
(acc, option, index) => {
|
||||
acc[index] = option;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<number, string>,
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}, [isEnum, options]);
|
||||
|
|
@ -85,8 +98,10 @@ function DynamicSlider({
|
|||
} else {
|
||||
setInputValue(value);
|
||||
}
|
||||
|
||||
updateSearchParams({ [settingKey]: value.toString() });
|
||||
},
|
||||
[isEnum, setInputValue, valueToEnumOption],
|
||||
[isEnum, setInputValue, valueToEnumOption, updateSearchParams, settingKey],
|
||||
);
|
||||
|
||||
const max = useMemo(() => {
|
||||
|
|
@ -117,7 +132,7 @@ function DynamicSlider({
|
|||
htmlFor={`${settingKey}-dynamic-setting`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label as TranslationKeys) ?? label : label || settingKey}{' '}
|
||||
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue})
|
||||
|
|
@ -132,7 +147,7 @@ function DynamicSlider({
|
|||
onChange={(value) => setInputValue(Number(value))}
|
||||
max={range ? range.max : (options?.length ?? 0) - 1}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
step={range ? (range.step ?? 1) : 1}
|
||||
controls={false}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
|
|
@ -164,19 +179,23 @@ function DynamicSlider({
|
|||
value={[
|
||||
isEnum
|
||||
? enumToNumeric[(selectedValue as number) ?? '']
|
||||
: (inputValue as number) ?? (defaultValue as number),
|
||||
: ((inputValue as number) ?? (defaultValue as number)),
|
||||
]}
|
||||
onValueChange={(value) => handleValueChange(value[0])}
|
||||
onDoubleClick={() => setInputValue(defaultValue as string | number)}
|
||||
max={max}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
step={range ? (range.step ?? 1) : 1}
|
||||
className="flex h-4 w-full"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description as TranslationKeys) ?? description : description}
|
||||
description={
|
||||
descriptionCode
|
||||
? (localize(description as TranslationKeys) ?? description)
|
||||
: description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useState, useMemo } from 'react';
|
|||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, Switch, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { TranslationKeys, useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { TranslationKeys, useLocalize, useParameterEffects, useUpdateSearchParams } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
|
|
@ -23,6 +23,7 @@ function DynamicSwitch({
|
|||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
const [inputValue, setInputValue] = useState<boolean>(!!(defaultValue as boolean | undefined));
|
||||
useParameterEffects({
|
||||
preset,
|
||||
|
|
@ -47,6 +48,7 @@ function DynamicSwitch({
|
|||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setInputValue(checked);
|
||||
updateSearchParams({ [settingKey]: checked.toString() });
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(checked);
|
||||
|
|
@ -65,7 +67,7 @@ function DynamicSwitch({
|
|||
htmlFor={`${settingKey}-dynamic-switch`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label as TranslationKeys) ?? label : label || settingKey}{' '}
|
||||
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}:{' '}
|
||||
|
|
@ -84,7 +86,11 @@ function DynamicSwitch({
|
|||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description as TranslationKeys) ?? description : description}
|
||||
description={
|
||||
descriptionCode
|
||||
? (localize(description as TranslationKeys) ?? description)
|
||||
: description
|
||||
}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { OptionTypes } from 'librechat-data-provider';
|
|||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, Input, HoverCard, HoverCardTrigger, Tag } from '~/components/ui';
|
||||
import { useChatContext, useToastContext } from '~/Providers';
|
||||
import { TranslationKeys, useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { cn, defaultTextProps } from '~/utils';
|
||||
import { TranslationKeys, useLocalize, useParameterEffects, useUpdateSearchParams } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
|
|
@ -30,6 +30,7 @@ function DynamicTags({
|
|||
const localize = useLocalize();
|
||||
const { preset } = useChatContext();
|
||||
const { showToast } = useToastContext();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [tagText, setTagText] = useState<string>('');
|
||||
const [tags, setTags] = useState<string[] | undefined>(
|
||||
|
|
@ -41,11 +42,13 @@ function DynamicTags({
|
|||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setTags(update);
|
||||
updateSearchParams({ [settingKey]: update.join(',') });
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(update);
|
||||
updateSearchParams({ [settingKey]: update.join(',') });
|
||||
},
|
||||
[optionType, setOption, settingKey],
|
||||
[optionType, setOption, settingKey, updateSearchParams],
|
||||
);
|
||||
|
||||
const onTagClick = useCallback(() => {
|
||||
|
|
@ -75,7 +78,7 @@ function DynamicTags({
|
|||
|
||||
if (minTags != null && currentTags.length <= minTags) {
|
||||
showToast({
|
||||
message: localize('com_ui_min_tags',{ 0: minTags + '' }),
|
||||
message: localize('com_ui_min_tags', { 0: minTags + '' }),
|
||||
status: 'warning',
|
||||
});
|
||||
return;
|
||||
|
|
@ -126,7 +129,7 @@ function DynamicTags({
|
|||
htmlFor={`${settingKey}-dynamic-input`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label as TranslationKeys) ?? label : label || settingKey}{' '}
|
||||
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
(
|
||||
|
|
@ -174,7 +177,11 @@ function DynamicTags({
|
|||
}
|
||||
}}
|
||||
onChange={(e) => setTagText(e.target.value)}
|
||||
placeholder={placeholderCode ? localize(placeholder as TranslationKeys) ?? placeholder : placeholder}
|
||||
placeholder={
|
||||
placeholderCode
|
||||
? (localize(placeholder as TranslationKeys) ?? placeholder)
|
||||
: placeholder
|
||||
}
|
||||
className={cn('flex h-10 max-h-10 border-none bg-surface-secondary px-3 py-2')}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -182,7 +189,11 @@ function DynamicTags({
|
|||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description as TranslationKeys) ?? description : description}
|
||||
description={
|
||||
descriptionCode
|
||||
? (localize(description as TranslationKeys) ?? description)
|
||||
: description
|
||||
}
|
||||
side={descriptionSide as ESide}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue