import type { FC } from 'react' import { useCallback } from 'react' import ReactDOM from 'react-dom' import { useTranslation } from 'react-i18next' import type { TextNode } from 'lexical' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { LexicalTypeaheadMenuPlugin, MenuOption, } from '@lexical/react/LexicalTypeaheadMenuPlugin' import { useBasicTypeaheadTriggerMatch } from '../hooks' import { INSERT_CONTEXT_BLOCK_COMMAND } from './context-block' import { INSERT_VARIABLE_BLOCK_COMMAND } from './variable-block' import { INSERT_HISTORY_BLOCK_COMMAND } from './history-block' import { INSERT_QUERY_BLOCK_COMMAND } from './query-block' import { File05 } from '@/app/components/base/icons/src/vender/solid/files' import { Variable } from '@/app/components/base/icons/src/vender/line/development' import { MessageClockCircle } from '@/app/components/base/icons/src/vender/solid/general' import { UserEdit02 } from '@/app/components/base/icons/src/vender/solid/users' class ComponentPickerOption extends MenuOption { title: string icon?: JSX.Element keywords: Array keyboardShortcut?: string desc: string onSelect: (queryString: string) => void disabled?: boolean constructor( title: string, options: { icon?: JSX.Element keywords?: Array keyboardShortcut?: string desc: string onSelect: (queryString: string) => void disabled?: boolean }, ) { super(title) this.title = title this.keywords = options.keywords || [] this.icon = options.icon this.keyboardShortcut = options.keyboardShortcut this.desc = options.desc this.onSelect = options.onSelect.bind(this) this.disabled = options.disabled } } type ComponentPickerMenuItemProps = { isSelected: boolean onClick: () => void onMouseEnter: () => void option: ComponentPickerOption } const ComponentPickerMenuItem: FC = ({ isSelected, onClick, onMouseEnter, option, }) => { const { t } = useTranslation() return (
{option.icon}
{option.title} {option.disabled && t('common.promptEditor.existed')}
{option.desc}
) } type ComponentPickerProps = { contextDisabled?: boolean historyDisabled?: boolean queryDisabled?: boolean contextShow?: boolean historyShow?: boolean queryShow?: boolean } const ComponentPicker: FC = ({ contextDisabled, historyDisabled, queryDisabled, contextShow, historyShow, queryShow, }) => { const { t } = useTranslation() const [editor] = useLexicalComposerContext() const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', { minLength: 0, maxLength: 0, }) const options = [ ...contextShow ? [ new ComponentPickerOption(t('common.promptEditor.context.item.title'), { desc: t('common.promptEditor.context.item.desc'), icon: , onSelect: () => { if (contextDisabled) return editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined) }, disabled: contextDisabled, }), ] : [], new ComponentPickerOption(t('common.promptEditor.variable.item.title'), { desc: t('common.promptEditor.variable.item.desc'), icon: , onSelect: () => { editor.dispatchCommand(INSERT_VARIABLE_BLOCK_COMMAND, undefined) }, }), ...historyShow ? [ new ComponentPickerOption(t('common.promptEditor.history.item.title'), { desc: t('common.promptEditor.history.item.desc'), icon: , onSelect: () => { if (historyDisabled) return editor.dispatchCommand(INSERT_HISTORY_BLOCK_COMMAND, undefined) }, disabled: historyDisabled, }), ] : [], ...queryShow ? [ new ComponentPickerOption(t('common.promptEditor.query.item.title'), { desc: t('common.promptEditor.query.item.desc'), icon: , onSelect: () => { if (queryDisabled) return editor.dispatchCommand(INSERT_QUERY_BLOCK_COMMAND, undefined) }, disabled: queryDisabled, }), ] : [], ] const onSelectOption = useCallback( ( selectedOption: ComponentPickerOption, nodeToRemove: TextNode | null, closeMenu: () => void, matchingString: string, ) => { editor.update(() => { if (nodeToRemove) nodeToRemove.remove() selectedOption.onSelect(matchingString) closeMenu() }) }, [editor], ) return ( {}} onSelectOption={onSelectOption} menuRenderFn={( anchorElementRef, { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, ) => (anchorElementRef.current && options.length) ? ReactDOM.createPortal(
{options.map((option, i: number) => ( { if (option.disabled) return setHighlightedIndex(i) selectOptionAndCleanUp(option) }} onMouseEnter={() => { if (option.disabled) return setHighlightedIndex(i) }} key={option.key} option={option} /> ))}
, anchorElementRef.current, ) : null} triggerFn={checkForTriggerMatch} /> ) } export default ComponentPicker