|
import React, { useState, useCallback, memo, ReactNode } from 'react'; |
|
import { Spinner } from '~/components'; |
|
import { useRecoilValue } from 'recoil'; |
|
import CodeBlock from './Content/CodeBlock.jsx'; |
|
import { Disclosure } from '@headlessui/react'; |
|
import { ChevronDownIcon, LucideProps } from 'lucide-react'; |
|
import { cn } from '~/utils/'; |
|
import store from '~/store'; |
|
|
|
interface Input { |
|
inputStr: string; |
|
} |
|
|
|
interface PluginProps { |
|
plugin: { |
|
plugin: string; |
|
input: string; |
|
thought: string; |
|
loading?: boolean; |
|
outputs?: string; |
|
latest?: string; |
|
inputs?: Input[]; |
|
}; |
|
} |
|
|
|
type PluginsMap = { |
|
[pluginKey: string]: string; |
|
}; |
|
|
|
type PluginIconProps = LucideProps & { |
|
className?: string; |
|
}; |
|
|
|
function formatInputs(inputs: Input[]) { |
|
let output = ''; |
|
|
|
for (let i = 0; i < inputs.length; i++) { |
|
output += `${inputs[i].inputStr}`; |
|
|
|
if (inputs.length > 1 && i !== inputs.length - 1) { |
|
output += ',\n'; |
|
} |
|
} |
|
|
|
return output; |
|
} |
|
|
|
const Plugin: React.FC<PluginProps> = ({ plugin }) => { |
|
const [loading, setLoading] = useState(plugin.loading); |
|
const finished = plugin.outputs && plugin.outputs.length > 0; |
|
const plugins: PluginsMap = useRecoilValue(store.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; |
|
} |
|
|
|
if (finished && loading) { |
|
setLoading(false); |
|
} |
|
|
|
const generateStatus = (): ReactNode => { |
|
if (!loading && latestPlugin === 'self reflection') { |
|
return 'Finished'; |
|
} else if (latestPlugin === 'self reflection') { |
|
return 'I\'m thinking...'; |
|
} else { |
|
return ( |
|
<> |
|
{loading ? 'Using' : 'Used'} <b>{latestPlugin}</b> |
|
{loading ? '...' : ''} |
|
</> |
|
); |
|
} |
|
}; |
|
|
|
return ( |
|
<div className="flex flex-col items-start"> |
|
<Disclosure> |
|
{({ open }) => { |
|
const iconProps: PluginIconProps = { |
|
className: cn(open ? 'rotate-180 transform' : '', 'h-4 w-4'), |
|
}; |
|
return ( |
|
<> |
|
<div |
|
className={cn( |
|
loading ? 'bg-green-100' : 'bg-[#ECECF1]', |
|
'flex items-center rounded p-3 text-sm text-gray-900', |
|
)} |
|
> |
|
<div> |
|
<div className="flex items-center gap-3"> |
|
<div>{generateStatus()}</div> |
|
</div> |
|
</div> |
|
{loading && <Spinner className="ml-1" />} |
|
<Disclosure.Button className="ml-12 flex items-center gap-2"> |
|
<ChevronDownIcon {...iconProps} /> |
|
</Disclosure.Button> |
|
</div> |
|
|
|
<Disclosure.Panel className="my-3 flex max-w-full flex-col gap-3"> |
|
<CodeBlock |
|
lang={latestPlugin?.toUpperCase() || 'INPUTS'} |
|
codeChildren={formatInputs(plugin.inputs ?? [])} |
|
plugin={true} |
|
classProp="max-h-[450px]" |
|
/> |
|
{finished && ( |
|
<CodeBlock |
|
lang="OUTPUTS" |
|
codeChildren={plugin.outputs ?? ''} |
|
plugin={true} |
|
classProp="max-h-[450px]" |
|
/> |
|
)} |
|
</Disclosure.Panel> |
|
</> |
|
); |
|
}} |
|
</Disclosure> |
|
</div> |
|
); |
|
}; |
|
|
|
export default memo(Plugin); |
|
|