| import { useCallback, memo, ReactNode } from 'react'; |
| import { Spinner } from '@librechat/client'; |
| import { ChevronDownIcon, LucideProps } from 'lucide-react'; |
| import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react'; |
| import type { TResPlugin, TInput } from 'librechat-data-provider'; |
| import { useGetEndpointsQuery } from '~/data-provider'; |
| import { useShareContext } from '~/Providers'; |
| import { cn, formatJSON } from '~/utils'; |
| import CodeBlock from './CodeBlock'; |
|
|
| type PluginIconProps = LucideProps & { |
| className?: string; |
| }; |
|
|
| function formatInputs(inputs: TInput[]) { |
| let output = ''; |
|
|
| for (let i = 0; i < inputs.length; i++) { |
| const input = formatJSON(`${inputs[i]?.inputStr ?? inputs[i]}`); |
| output += input; |
|
|
| if (inputs.length > 1 && i !== inputs.length - 1) { |
| output += ',\n'; |
| } |
| } |
|
|
| return output; |
| } |
|
|
| type PluginProps = { |
| plugin: TResPlugin; |
| }; |
|
|
| const Plugin: React.FC<PluginProps> = ({ plugin }) => { |
| const { isSharedConvo } = useShareContext(); |
| const { data: plugins = {} } = useGetEndpointsQuery({ |
| enabled: !isSharedConvo, |
| select: (data) => data?.gptPlugins?.plugins, |
| }); |
|
|
| const getPluginName = useCallback( |
| (pluginKey: string) => { |
| if (!pluginKey) { |
| return null; |
| } |
|
|
| if (pluginKey === 'n/a' || pluginKey === 'self reflection') { |
| return pluginKey; |
| } |
| return plugins[pluginKey] ?? 'self reflection'; |
| }, |
| [plugins], |
| ); |
|
|
| if (!plugin || !plugin.latest) { |
| return null; |
| } |
|
|
| const latestPlugin = getPluginName(plugin.latest); |
|
|
| if (!latestPlugin || (latestPlugin && latestPlugin === 'n/a')) { |
| return null; |
| } |
|
|
| const generateStatus = (): ReactNode => { |
| if (!plugin.loading && latestPlugin === 'self reflection') { |
| return 'Finished'; |
| } else if (latestPlugin === 'self reflection') { |
| return "I'm thinking..."; |
| } else { |
| return ( |
| <> |
| {plugin.loading ? 'Using' : 'Used'} <b>{latestPlugin}</b> |
| {plugin.loading ? '...' : ''} |
| </> |
| ); |
| } |
| }; |
|
|
| return ( |
| <div className="my-2 flex flex-col items-start"> |
| <Disclosure> |
| {({ open }) => { |
| const iconProps: PluginIconProps = { |
| className: cn(open ? 'rotate-180 transform' : '', 'h-4 w-4'), |
| }; |
| return ( |
| <> |
| <div |
| className={cn( |
| plugin.loading ? 'bg-green-100' : 'bg-gray-20', |
| 'my-1 flex items-center rounded p-3 text-xs text-gray-800', |
| )} |
| > |
| <div> |
| <div className="flex items-center gap-3"> |
| <div>{generateStatus()}</div> |
| </div> |
| </div> |
| {plugin.loading && <Spinner className="ml-1 text-black" />} |
| <DisclosureButton className="ml-12 flex items-center gap-2"> |
| <ChevronDownIcon {...iconProps} /> |
| </DisclosureButton> |
| </div> |
| |
| <DisclosurePanel className="mt-3 flex max-w-full flex-col gap-3"> |
| <CodeBlock |
| lang={latestPlugin ? `REQUEST TO ${latestPlugin.toUpperCase()}` : 'REQUEST'} |
| codeChildren={formatInputs(plugin.inputs ?? [])} |
| plugin={true} |
| classProp="max-h-[450px]" |
| /> |
| {plugin.outputs && plugin.outputs.length > 0 && ( |
| <CodeBlock |
| lang={latestPlugin ? `RESPONSE FROM ${latestPlugin.toUpperCase()}` : 'RESPONSE'} |
| codeChildren={formatJSON(plugin.outputs ?? '')} |
| plugin={true} |
| classProp="max-h-[450px]" |
| /> |
| )} |
| </DisclosurePanel> |
| </> |
| ); |
| }} |
| </Disclosure> |
| </div> |
| ); |
| }; |
|
|
| export default memo(Plugin); |
|
|