Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function DynamicCheckbox({
>
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
{showDefault && (
<small className="opacity-40 high-contrast:opacity-100">
<small className="high-contrast:opacity-100 opacity-40">
({localize('com_endpoint_default')}:{' '}
{defaultValue != null ? localize('com_ui_yes') : localize('com_ui_no')})
</small>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function DynamicCombobox({
)}
>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<HoverCardTrigger className="grid h-full w-full content-between items-center gap-2">
{showLabel === true && (
<div className="flex w-full justify-between">
<Label
Expand All @@ -86,7 +86,7 @@ function DynamicCombobox({
>
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}
{showDefault && (
<small className="opacity-40 high-contrast:opacity-100">
<small className="high-contrast:opacity-100 opacity-40">
({localize('com_endpoint_default')}: {defaultValue})
</small>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function DynamicDropdown({
)}
>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<HoverCardTrigger className="grid h-full w-full content-between items-center gap-2">
{showLabel === true && (
<div className="flex w-full justify-between">
<Label
Expand All @@ -80,7 +80,7 @@ function DynamicDropdown({
>
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}
{showDefault && (
<small className="opacity-40 high-contrast:opacity-100">
<small className="high-contrast:opacity-100 opacity-40">
({localize('com_endpoint_default')}: {defaultValue})
</small>
)}
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/SidePanel/Parameters/DynamicInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,15 @@ function DynamicInput({
}`}
>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<HoverCardTrigger className="grid h-full w-full content-between items-center gap-2">
<div className="flex w-full justify-between">
<Label
htmlFor={`${settingKey}-dynamic-input`}
className="text-left text-xs font-medium"
>
{labelCode ? localize(label as TranslationKeys) || label : label || settingKey}{' '}
{showDefault && (
<small className="opacity-40 high-contrast:opacity-100">
<small className="high-contrast:opacity-100 opacity-40">
(
{typeof defaultValue === 'undefined' || !(defaultValue as string).length
? localize('com_endpoint_default_blank')
Expand All @@ -197,7 +197,7 @@ function DynamicInput({
onBlur={handleInputBlur}
placeholder={placeholderText}
className={cn(
'flex h-9 max-h-9 w-full resize-none rounded-lg border border-border-light bg-surface-secondary px-3 py-2',
'border-border-light bg-surface-secondary flex h-9 max-h-9 w-full resize-none rounded-lg border px-3 py-2',
)}
/>
</HoverCardTrigger>
Expand Down
10 changes: 5 additions & 5 deletions client/src/components/SidePanel/Parameters/DynamicSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,15 @@ function DynamicSlider({
)}
>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<HoverCardTrigger className="grid h-full w-full content-between items-center gap-2">
<div className="flex w-full items-center justify-between">
<Label
htmlFor={`${settingKey}-dynamic-setting`}
className="break-words text-left text-xs font-medium"
className="text-left text-xs font-medium break-words"
>
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
{showDefault && (
<small className="opacity-40 high-contrast:opacity-100">
<small className="high-contrast:opacity-100 opacity-40">
({localize('com_endpoint_default')}: {getDefaultDisplayValue()})
</small>
)}
Expand All @@ -202,7 +202,7 @@ function DynamicSlider({
defaultTextProps,
cn(
optionText,
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 py-1 text-xs group-hover/temp:border-border-light',
'reset-rc-number-input reset-rc-number-input-text-right group-hover/temp:border-border-light h-auto w-12 border-0 py-1 text-xs',
),
)}
/>
Expand All @@ -217,7 +217,7 @@ function DynamicSlider({
defaultTextProps,
cn(
optionText,
'reset-rc-number-input h-auto w-14 border-0 py-1 pl-1 text-center text-xs group-hover/temp:border-border-light',
'reset-rc-number-input group-hover/temp:border-border-light h-auto w-14 border-0 py-1 pl-1 text-center text-xs',
),
)}
/>
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/SidePanel/Parameters/DynamicSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ function DynamicSwitch({
}`}
>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<HoverCardTrigger className="grid h-full w-full content-between items-center gap-2">
<div className="flex justify-between">
<Label
htmlFor={`${settingKey}-dynamic-switch`}
className="break-words text-left text-xs font-medium"
className="text-left text-xs font-medium break-words"
>
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
{showDefault && (
<small className="opacity-40 high-contrast:opacity-100">
<small className="high-contrast:opacity-100 opacity-40">
({localize('com_endpoint_default')}:{' '}
{defaultValue != null ? localize('com_ui_on') : localize('com_ui_off')})
</small>
Expand Down
8 changes: 4 additions & 4 deletions client/src/components/SidePanel/Parameters/DynamicTags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,15 @@ function DynamicTags({
}`}
>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<HoverCardTrigger className="grid h-full w-full content-between items-center gap-2">
<div className="flex w-full justify-between">
<Label
htmlFor={`${settingKey}-dynamic-input`}
className="text-left text-xs font-medium"
>
{labelCode ? (localize(label as TranslationKeys) ?? label) : label || settingKey}{' '}
{showDefault && (
<small className="opacity-40 high-contrast:opacity-100">
<small className="high-contrast:opacity-100 opacity-40">
(
{typeof defaultValue === 'undefined' || !(defaultValue as string).length
? localize('com_endpoint_default_blank')
Expand All @@ -125,7 +125,7 @@ function DynamicTags({
</Label>
</div>
<div>
<div className="mb-2 flex flex-wrap break-all rounded-lg border border-border-light bg-surface-secondary">
<div className="border-border-light bg-surface-secondary flex flex-wrap rounded-lg border break-all">
{currentTags && currentTags.length > 0 && (
<div className="flex w-full gap-1 p-1">
{currentTags.map((tag: string, index: number) => (
Expand Down Expand Up @@ -166,7 +166,7 @@ function DynamicTags({
? (localize(placeholder as TranslationKeys) ?? placeholder)
: placeholder
}
className={cn('flex h-9 max-h-9 border-none bg-surface-secondary px-3 py-2')}
className={cn('bg-surface-secondary flex h-9 max-h-9 border-none px-3 py-2')}
/>
</div>
</div>
Expand Down
10 changes: 8 additions & 2 deletions client/src/components/SidePanel/Parameters/DynamicTextarea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function DynamicTextarea({
}`}
>
<HoverCard openDelay={300}>
<HoverCardTrigger className="grid w-full items-center gap-2">
<HoverCardTrigger className="grid h-full w-full content-between items-center gap-2">
<div className="flex w-full justify-between">
<Label
htmlFor={`${settingKey}-dynamic-textarea`}
Expand All @@ -75,6 +75,12 @@ function DynamicTextarea({
<TextareaAutosize
focusOutline="hidden"
id={`${settingKey}-dynamic-textarea`}
/** The field is measured by a shadow copy of itself, and the panel mounts
* before the sidebar has settled on a width, so that measurement can come
* back as one word per line. Capping the rows lets the library clamp its
* own answer instead of leaving a CSS max-height to hide a wrong one. */
minRows={3}
maxRows={8}
disabled={readonly}
value={inputValue ?? ''}
onChange={setInputValue}
Expand All @@ -88,7 +94,7 @@ function DynamicTextarea({
: placeholder
}
className={cn(
'border-border-light bg-surface-secondary flex max-h-[138px] min-h-[100px] w-full resize-none rounded-lg border px-3 py-2 text-sm',
'border-border-light bg-surface-secondary flex max-h-[220px] min-h-[76px] w-full resize-none rounded-lg border px-3 py-2 text-sm',
)}
/>
</HoverCardTrigger>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/SidePanel/Parameters/OptionHover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function OptionHover({
<HoverCardPortal>
<HoverCardContent side={side} className={`z-[999] w-80 ${className}`} sideOffset={sideOffset}>
<div className="space-y-2">
<p className="whitespace-pre-wrap text-sm text-text-secondary">{text}</p>
<p className="text-text-secondary text-sm whitespace-pre-wrap">{text}</p>
</div>
</HoverCardContent>
</HoverCardPortal>
Expand Down
137 changes: 101 additions & 36 deletions client/src/components/SidePanel/Parameters/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useState, useEffect, useCallback } from 'react';
import React, { useId, useMemo, useState, useEffect, useCallback } from 'react';
import keyBy from 'lodash/keyBy';
import { RotateCcw } from 'lucide-react';
import { Button } from '@librechat/client';
Expand All @@ -14,6 +14,7 @@ import {
resolveDropParamsUIKeys,
} from 'librechat-data-provider';
import type { TPreset } from 'librechat-data-provider';
import { groupParameters, countModified, hasControl, isWideParameter } from './groups';
import { useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider';
import { useChatContext, useLiveAnnouncer } from '~/Providers';
import { SaveAsPresetDialog } from '~/components/Endpoints';
Expand All @@ -22,6 +23,7 @@ import { componentMapping } from './components';
import { logger, cn } from '~/utils';

export default function Parameters() {
const panelId = useId();
const localize = useLocalize();
const { data: startupConfig } = useGetStartupConfig();
const { conversation, setConversation } = useChatContext();
Expand Down Expand Up @@ -164,6 +166,23 @@ export default function Parameters() {
setResetCount((count) => count + 1);
}, [setConversation, announcePolite, localize]);

/** Region choices come from the deployment, so they are filled in before grouping,
* and a control left with nothing to render is dropped there too: a section is
* built only from controls that show something. */
const sections = useMemo(
() =>
groupParameters(
visibleParameters
.map((setting) =>
setting.key === 'region' && bedrockRegions.length > 0
? { ...setting, options: bedrockRegions }
: setting,
)
.filter((setting) => componentMapping[setting.component] != null && hasControl(setting)),
),
[visibleParameters, bedrockRegions],
);

const openDialog = useCallback(() => {
const newPreset = tConvoUpdateSchema.parse({
...conversation,
Expand All @@ -177,57 +196,103 @@ export default function Parameters() {
}

return (
<div className="h-auto max-w-full px-3 pb-3 pt-2">
<div className="grid grid-cols-2 gap-4">
{' '}
{/* This is the parent element containing all settings */}
{/* Below is an example of an applied dynamic setting, each be contained by a div with the column span specified */}
{visibleParameters.map((setting) => {
const Component = componentMapping[setting.component];
if (!Component) {
return null;
}
const { key, default: defaultValue, ...rest } = setting;

if (key === 'region' && bedrockRegions.length) {
rest.options = bedrockRegions;
}

return (
<Component
key={key}
settingKey={key}
defaultValue={defaultValue}
{...rest}
setOption={setOption}
conversation={conversation}
/>
);
})}
</div>
<div className="mt-4 flex justify-center">
<div className="h-auto max-w-full px-3 pt-1 pb-3">
{/* Every parameter this model can act on is on screen. A disclosure would
trade the one thing a settings panel is for, seeing the current state at a
glance, for vertical space that pairing and quieter headings give back
anyway. */}
{sections.map((section) => {
const changed = countModified(section.settings, conversation);
const headingId = `${panelId}-${section.id}`;

return (
<section key={section.id} aria-labelledby={headingId} className="pt-4 first:pt-0">
<h3
id={headingId}
className="text-text-secondary mb-2 flex items-center gap-1.5 text-xs font-semibold tracking-wide uppercase"
>
<span className="truncate">{localize(section.label)}</span>
{/* Where this conversation's own answers are, which is what the owner
scans for before reaching for Reset. */}
{changed > 0 && (
<>
<span
aria-hidden="true"
className="bg-surface-tertiary text-text-primary shrink-0 rounded-full px-1.5 text-xs font-normal tracking-normal normal-case tabular-nums"
>
{changed}
</span>
<span className="sr-only">
{localize(
changed === 1
? 'com_ui_params_changed_count_one'
: 'com_ui_params_changed_count',
{ count: changed },
)}
</span>
</>
)}
</h3>
<div className="grid grid-cols-2 gap-x-3 gap-y-2.5">
{section.settings.map((setting) => {
const Component = componentMapping[setting.component];
if (!Component) {
return null;
}
const { key, default: defaultValue, ...rest } = setting;

/** The cell owns the span, not the control. The definitions carry a
* columnSpan written for the four-column preset dialog, which says
* nothing about a panel this narrow. */
/** The cell stretches its control to the row, so a pair whose labels
* wrap differently still lines their inputs up. Applied here rather
* than in the shared controls, which the preset editors reuse. */
return (
<div
key={key}
className={cn(
'*:h-full',
isWideParameter(setting) ? 'col-span-2' : 'col-span-1',
)}
>
<Component
settingKey={key}
defaultValue={defaultValue}
{...rest}
setOption={setOption}
conversation={conversation}
/>
</div>
);
})}
</div>
</section>
);
})}
{/* The two share a row while their labels fit, stack when a translation is too
long for the panel, and a label longer than the panel itself wraps. */}
<div className="mt-5 flex flex-wrap gap-2">
<Button
variant="outline"
type="button"
onClick={resetParameters}
className="flex w-full items-center justify-center gap-2 px-4 py-2 text-sm active:scale-[0.98] motion-reduce:transform-none"
aria-label={localize('com_ui_reset_var', { 0: localize('com_ui_model_parameters') })}
className="flex h-auto min-h-9 flex-auto items-center justify-center gap-2 px-4 py-2 text-sm whitespace-normal active:scale-[0.98] motion-reduce:transform-none"
>
<RotateCcw
key={resetCount}
className={cn(
'h-4 w-4 motion-reduce:animate-none',
'h-4 w-4 shrink-0 motion-reduce:animate-none',
resetCount > 0 && 'animate-reset-spin',
)}
aria-hidden="true"
/>
{localize('com_ui_reset_var', { 0: localize('com_ui_model_parameters') })}
{localize('com_ui_reset')}
</Button>
</div>
<div className="mt-2 flex justify-center">
<Button
variant="default"
onClick={openDialog}
className="flex w-full items-center justify-center px-4 py-2 font-semibold"
className="flex h-auto min-h-9 flex-auto items-center justify-center px-4 py-2 font-semibold whitespace-normal"
type="button"
>
{localize('com_endpoint_save_as_preset')}
Expand Down
Loading
Loading