mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
📦 feat: Model & Assistants Combobox for Side Panel (#2380)
* WIP: dynamic settings * WIP: update tests and validations * refactor(SidePanel): use hook for Links * WIP: dynamic settings, slider implemented * feat(useDebouncedInput): dynamic typing with generic * refactor(generate): add `custom` optionType to be non-conforming to conversation schema * feat: DynamicDropdown * refactor(DynamicSlider): custom optionType handling and useEffect for conversation updates elsewhere * refactor(Panel): add more test cases * chore(DynamicSlider): note * refactor(useDebouncedInput): import defaultDebouncedDelay from ~/common` * WIP: implement remaining ComponentTypes * chore: add com_sidepanel_parameters * refactor: add langCode handling for dynamic settings * chore(useOriginNavigate): change path to '/c/' * refactor: explicit textarea focus on new convo, share textarea idea via ~/common * refactor: useParameterEffects: reset if convo or preset Ids change, share and maintain statefulness in side panel * wip: combobox * chore: minor styling for Select components * wip: combobox select styling for side panel * feat: complete combobox * refactor: model select for side panel switcher * refactor(Combobox): add portal * chore: comment out dynamic parameters panel for future PR and delete prompt files * refactor(Combobox): add icon field for options, change hover bg-color, add displayValue * fix(useNewConvo): proper textarea focus with setTimeout * refactor(AssistantSwitcher): use Combobox * refactor(ModelSwitcher): add textarea focus on model switch
This commit is contained in:
parent
f64a2cb0b0
commit
8e5f1ad575
33 changed files with 2850 additions and 462 deletions
|
|
@ -1,104 +1,20 @@
|
|||
import { useEffect } from 'react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '~/components/ui/Select';
|
||||
import { EModelEndpoint, defaultOrderQuery } from 'librechat-data-provider';
|
||||
import { useSetIndexOptions, useSelectAssistant, useLocalize } from '~/hooks';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
import Icon from '~/components/Endpoints/Icon';
|
||||
import { cn } from '~/utils';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { SwitcherProps } from '~/common';
|
||||
import AssistantSwitcher from './AssistantSwitcher';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import ModelSwitcher from './ModelSwitcher';
|
||||
|
||||
interface SwitcherProps {
|
||||
isCollapsed: boolean;
|
||||
}
|
||||
|
||||
export default function Switcher({ isCollapsed }: SwitcherProps) {
|
||||
const localize = useLocalize();
|
||||
const { setOption } = useSetIndexOptions();
|
||||
const { index, conversation } = useChatContext();
|
||||
|
||||
/* `selectedAssistant` must be defined with `null` to cause re-render on update */
|
||||
const { assistant_id: selectedAssistant = null, endpoint } = conversation ?? {};
|
||||
|
||||
const { data: assistants = [] } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) => res.data.map(({ id, name, metadata }) => ({ id, name, metadata })),
|
||||
});
|
||||
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
const { onSelect } = useSelectAssistant();
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedAssistant && assistants && assistants.length && assistantMap) {
|
||||
const assistant_id =
|
||||
localStorage.getItem(`assistant_id__${index}`) ?? assistants[0]?.id ?? '';
|
||||
const assistant = assistantMap?.[assistant_id];
|
||||
if (!assistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (endpoint !== EModelEndpoint.assistants) {
|
||||
return;
|
||||
}
|
||||
setOption('model')(assistant.model);
|
||||
setOption('assistant_id')(assistant_id);
|
||||
}
|
||||
}, [index, assistants, selectedAssistant, assistantMap, endpoint, setOption]);
|
||||
|
||||
const currentAssistant = assistantMap?.[selectedAssistant ?? ''];
|
||||
|
||||
return (
|
||||
<Select value={selectedAssistant as string | undefined} onValueChange={onSelect}>
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
'flex items-center gap-2 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0',
|
||||
isCollapsed
|
||||
? 'flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden'
|
||||
: '',
|
||||
'bg-white text-black hover:bg-gray-50 dark:bg-gray-850 dark:text-white',
|
||||
)}
|
||||
aria-label={localize('com_sidepanel_select_assistant')}
|
||||
>
|
||||
<SelectValue placeholder={localize('com_sidepanel_select_assistant')}>
|
||||
<div className="assistant-item flex items-center justify-center overflow-hidden rounded-full">
|
||||
<Icon
|
||||
isCreatedByUser={false}
|
||||
endpoint={EModelEndpoint.assistants}
|
||||
assistantName={currentAssistant?.name ?? ''}
|
||||
iconURL={(currentAssistant?.metadata?.avatar as string) ?? ''}
|
||||
/>
|
||||
</div>
|
||||
<span className={cn('ml-2', isCollapsed ? 'hidden' : '')} style={{ userSelect: 'none' }}>
|
||||
{assistants.find((assistant) => assistant.id === selectedAssistant)?.name ??
|
||||
localize('com_sidepanel_select_assistant')}
|
||||
</span>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-white dark:bg-gray-800">
|
||||
{assistants.map((assistant) => (
|
||||
<SelectItem
|
||||
key={assistant.id}
|
||||
value={assistant.id}
|
||||
className="hover:bg-gray-50 dark:text-white"
|
||||
>
|
||||
<div className="[&_svg]:text-foreground flex items-center justify-center gap-3 dark:text-white [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0">
|
||||
<div className="assistant-item overflow-hidden rounded-full ">
|
||||
<Icon
|
||||
isCreatedByUser={false}
|
||||
endpoint={EModelEndpoint.assistants}
|
||||
assistantName={assistant.name ?? ''}
|
||||
iconURL={(assistant.metadata?.avatar as string) ?? ''}
|
||||
/>
|
||||
</div>
|
||||
{assistant.name}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
export default function Switcher(props: SwitcherProps) {
|
||||
const { conversation } = useChatContext();
|
||||
const { endpoint } = conversation ?? {};
|
||||
|
||||
if (!props.endpointKeyProvided) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (endpoint === EModelEndpoint.assistants) {
|
||||
return <AssistantSwitcher {...props} />;
|
||||
}
|
||||
|
||||
return <ModelSwitcher {...props} />;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue