| import React, { useMemo, useState, useEffect, useCallback } from 'react'; |
| import keyBy from 'lodash/keyBy'; |
| import { RotateCcw } from 'lucide-react'; |
| import { |
| excludedKeys, |
| paramSettings, |
| getSettingsKeys, |
| getEndpointField, |
| SettingDefinition, |
| tConvoUpdateSchema, |
| } from 'librechat-data-provider'; |
| import type { TPreset } from 'librechat-data-provider'; |
| import { SaveAsPresetDialog } from '~/components/Endpoints'; |
| import { useSetIndexOptions, useLocalize } from '~/hooks'; |
| import { useGetEndpointsQuery } from '~/data-provider'; |
| import { componentMapping } from './components'; |
| import { useChatContext } from '~/Providers'; |
| import { logger } from '~/utils'; |
|
|
| export default function Parameters() { |
| const localize = useLocalize(); |
| const { conversation, setConversation } = useChatContext(); |
| const { setOption } = useSetIndexOptions(); |
|
|
| const [isDialogOpen, setIsDialogOpen] = useState(false); |
| const [preset, setPreset] = useState<TPreset | null>(null); |
|
|
| const { data: endpointsConfig = {} } = useGetEndpointsQuery(); |
| const provider = conversation?.endpoint ?? ''; |
| const model = conversation?.model ?? ''; |
|
|
| const bedrockRegions = useMemo(() => { |
| return endpointsConfig?.[conversation?.endpoint ?? '']?.availableRegions ?? []; |
| }, [endpointsConfig, conversation?.endpoint]); |
|
|
| const endpointType = useMemo( |
| () => getEndpointField(endpointsConfig, conversation?.endpoint, 'type'), |
| [conversation?.endpoint, endpointsConfig], |
| ); |
|
|
| const parameters = useMemo((): SettingDefinition[] => { |
| const customParams = endpointsConfig[provider]?.customParams ?? {}; |
| const [combinedKey, endpointKey] = getSettingsKeys(endpointType ?? provider, model); |
| const overriddenEndpointKey = customParams.defaultParamsEndpoint ?? endpointKey; |
| const defaultParams = paramSettings[combinedKey] ?? paramSettings[overriddenEndpointKey] ?? []; |
| const overriddenParams = endpointsConfig[provider]?.customParams?.paramDefinitions ?? []; |
| const overriddenParamsMap = keyBy(overriddenParams, 'key'); |
| return defaultParams |
| .filter((param) => param != null) |
| .map((param) => (overriddenParamsMap[param.key] as SettingDefinition) ?? param); |
| }, [endpointType, endpointsConfig, model, provider]); |
|
|
| useEffect(() => { |
| if (!parameters) { |
| return; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const paramKeys = new Set( |
| parameters.filter((setting) => setting != null).map((setting) => setting.key), |
| ); |
| setConversation((prev) => { |
| if (!prev) { |
| return prev; |
| } |
|
|
| const updatedConversation = { ...prev }; |
|
|
| const conversationKeys = Object.keys(updatedConversation); |
| const updatedKeys: string[] = []; |
| conversationKeys.forEach((key) => { |
| |
| |
| |
| |
| |
| |
|
|
| if (paramKeys.has(key)) { |
| return; |
| } |
|
|
| if (excludedKeys.has(key)) { |
| return; |
| } |
|
|
| if (prev[key] != null) { |
| updatedKeys.push(key); |
| delete updatedConversation[key]; |
| } |
| }); |
|
|
| logger.log('parameters', 'parameters effect, updated keys:', updatedKeys); |
|
|
| return updatedConversation; |
| }); |
| }, [parameters, setConversation]); |
|
|
| const resetParameters = useCallback(() => { |
| setConversation((prev) => { |
| if (!prev) { |
| return prev; |
| } |
|
|
| const updatedConversation = { ...prev }; |
| const resetKeys: string[] = []; |
|
|
| Object.keys(updatedConversation).forEach((key) => { |
| if (excludedKeys.has(key)) { |
| return; |
| } |
|
|
| if (updatedConversation[key] !== undefined) { |
| resetKeys.push(key); |
| delete updatedConversation[key]; |
| } |
| }); |
|
|
| logger.log('parameters', 'parameters reset, affected keys:', resetKeys); |
| return updatedConversation; |
| }); |
| }, [setConversation]); |
|
|
| const openDialog = useCallback(() => { |
| const newPreset = tConvoUpdateSchema.parse({ |
| ...conversation, |
| }) as TPreset; |
| setPreset(newPreset); |
| setIsDialogOpen(true); |
| }, [conversation]); |
|
|
| if (!parameters) { |
| return null; |
| } |
|
|
| return ( |
| <div className="h-auto max-w-full overflow-x-hidden p-3"> |
| <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 */} |
| {parameters.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"> |
| <button |
| type="button" |
| onClick={resetParameters} |
| className="btn btn-neutral flex w-full items-center justify-center gap-2 px-4 py-2 text-sm" |
| > |
| <RotateCcw className="h-4 w-4" aria-hidden="true" /> |
| {localize('com_ui_reset_var', { 0: localize('com_ui_model_parameters') })} |
| </button> |
| </div> |
| <div className="mt-2 flex justify-center"> |
| <button |
| onClick={openDialog} |
| className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-green-600 focus:border-green-500" |
| type="button" |
| > |
| {localize('com_endpoint_save_as_preset')} |
| </button> |
| </div> |
| {preset && ( |
| <SaveAsPresetDialog open={isDialogOpen} onOpenChange={setIsDialogOpen} preset={preset} /> |
| )} |
| </div> |
| ); |
| } |
|
|