File size: 4,055 Bytes
b9fe2b4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
import { CodeHighlightNode, CodeNode } from '@lexical/code';
import {
InitialConfigType,
LexicalComposer,
} from '@lexical/react/LexicalComposer';
import { ContentEditable } from '@lexical/react/LexicalContentEditable';
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
import {
$getRoot,
$getSelection,
$nodesOfType,
EditorState,
Klass,
LexicalNode,
} from 'lexical';
import { cn } from '@/lib/utils';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { Variable } from 'lucide-react';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
import theme from './theme';
import { VariableNode } from './variable-node';
import { VariableOnChangePlugin } from './variable-on-change-plugin';
import VariablePickerMenuPlugin from './variable-picker-plugin';
// Catch any errors that occur during Lexical updates and log them
// or throw them as needed. If you don't throw them, Lexical will
// try to recover gracefully without losing user data.
function onError(error: Error) {
console.error(error);
}
const Nodes: Array<Klass<LexicalNode>> = [
HeadingNode,
QuoteNode,
CodeHighlightNode,
CodeNode,
VariableNode,
];
type IProps = {
value?: string;
onChange?: (value?: string) => void;
};
function PromptContent() {
const [editor] = useLexicalComposerContext();
const [isBlur, setIsBlur] = useState(false);
const { t } = useTranslation();
const insertTextAtCursor = useCallback(() => {
editor.update(() => {
const selection = $getSelection();
if (selection !== null) {
selection.insertText(' /');
}
});
}, [editor]);
const handleVariableIconClick = useCallback(() => {
insertTextAtCursor();
}, [insertTextAtCursor]);
const handleBlur = useCallback(() => {
setIsBlur(true);
}, []);
const handleFocus = useCallback(() => {
setIsBlur(false);
}, []);
return (
<section
className={cn('border rounded-sm ', { 'border-blue-400': !isBlur })}
>
<div className="border-b px-2 py-2 justify-end flex">
<Tooltip>
<TooltipTrigger asChild>
<span className="inline-block cursor-pointer cursor p-0.5 hover:bg-gray-100 dark:hover:bg-slate-800 rounded-sm">
<Variable size={16} onClick={handleVariableIconClick} />
</span>
</TooltipTrigger>
<TooltipContent>
<p>{t('flow.insertVariableTip')}</p>
</TooltipContent>
</Tooltip>
</div>
<ContentEditable
className="min-h-40 relative px-2 py-1 focus-visible:outline-none"
onBlur={handleBlur}
onFocus={handleFocus}
/>
</section>
);
}
export function PromptEditor({ value, onChange }: IProps) {
const { t } = useTranslation();
const initialConfig: InitialConfigType = {
namespace: 'PromptEditor',
theme,
onError,
nodes: Nodes,
};
const onValueChange = useCallback(
(editorState: EditorState) => {
editorState?.read(() => {
const listNodes = $nodesOfType(VariableNode); // to be removed
// const allNodes = $dfs();
console.log('π ~ onChange ~ allNodes:', listNodes);
const text = $getRoot().getTextContent();
onChange?.(text);
});
},
[onChange],
);
return (
<LexicalComposer initialConfig={initialConfig}>
<RichTextPlugin
contentEditable={<PromptContent></PromptContent>}
placeholder={
<div className="absolute top-2 left-2">{t('common.pleaseInput')}</div>
}
ErrorBoundary={LexicalErrorBoundary}
/>
<VariablePickerMenuPlugin value={value}></VariablePickerMenuPlugin>
<VariableOnChangePlugin onChange={onValueChange}></VariableOnChangePlugin>
</LexicalComposer>
);
}
|