index.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. 'use client'
  2. import type { FC } from 'react'
  3. import { useEffect } from 'react'
  4. import type {
  5. EditorState,
  6. } from 'lexical'
  7. import {
  8. $getRoot,
  9. TextNode,
  10. } from 'lexical'
  11. import { LexicalComposer } from '@lexical/react/LexicalComposer'
  12. import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
  13. import { ContentEditable } from '@lexical/react/LexicalContentEditable'
  14. import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'
  15. import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
  16. // import TreeView from './plugins/tree-view'
  17. import Placeholder from './plugins/placeholder'
  18. import ComponentPicker from './plugins/component-picker'
  19. import VariablePicker from './plugins/variable-picker'
  20. import ContextBlock from './plugins/context-block'
  21. import { ContextBlockNode } from './plugins/context-block/node'
  22. import ContextBlockReplacementBlock from './plugins/context-block-replacement-block'
  23. import HistoryBlock from './plugins/history-block'
  24. import { HistoryBlockNode } from './plugins/history-block/node'
  25. import HistoryBlockReplacementBlock from './plugins/history-block-replacement-block'
  26. import QueryBlock from './plugins/query-block'
  27. import { QueryBlockNode } from './plugins/query-block/node'
  28. import QueryBlockReplacementBlock from './plugins/query-block-replacement-block'
  29. import VariableBlock from './plugins/variable-block'
  30. import VariableValueBlock from './plugins/variable-value-block'
  31. import { VariableValueBlockNode } from './plugins/variable-value-block/node'
  32. import { CustomTextNode } from './plugins/custom-text/node'
  33. import OnBlurBlock from './plugins/on-blur-block'
  34. import { textToEditorState } from './utils'
  35. import type { Dataset } from './plugins/context-block'
  36. import type { RoleName } from './plugins/history-block'
  37. import type { Option } from './plugins/variable-picker'
  38. import {
  39. UPDATE_DATASETS_EVENT_EMITTER,
  40. UPDATE_HISTORY_EVENT_EMITTER,
  41. } from './constants'
  42. import { useEventEmitterContextContext } from '@/context/event-emitter'
  43. export type PromptEditorProps = {
  44. className?: string
  45. value?: string
  46. editable?: boolean
  47. onChange?: (text: string) => void
  48. onBlur?: () => void
  49. contextBlock?: {
  50. show?: boolean
  51. selectable?: boolean
  52. datasets: Dataset[]
  53. onInsert?: () => void
  54. onDelete?: () => void
  55. onAddContext: () => void
  56. }
  57. variableBlock?: {
  58. selectable?: boolean
  59. variables: Option[]
  60. }
  61. historyBlock?: {
  62. show?: boolean
  63. selectable?: boolean
  64. history: RoleName
  65. onInsert?: () => void
  66. onDelete?: () => void
  67. onEditRole: () => void
  68. }
  69. queryBlock?: {
  70. show?: boolean
  71. selectable?: boolean
  72. onInsert?: () => void
  73. onDelete?: () => void
  74. }
  75. }
  76. const PromptEditor: FC<PromptEditorProps> = ({
  77. className,
  78. value,
  79. editable = true,
  80. onChange,
  81. onBlur,
  82. contextBlock = {
  83. show: true,
  84. selectable: true,
  85. datasets: [],
  86. onAddContext: () => {},
  87. onInsert: () => {},
  88. onDelete: () => {},
  89. },
  90. historyBlock = {
  91. show: true,
  92. selectable: true,
  93. history: {
  94. user: '',
  95. assistant: '',
  96. },
  97. onEditRole: () => {},
  98. onInsert: () => {},
  99. onDelete: () => {},
  100. },
  101. variableBlock = {
  102. variables: [],
  103. },
  104. queryBlock = {
  105. show: true,
  106. selectable: true,
  107. onInsert: () => {},
  108. onDelete: () => {},
  109. },
  110. }) => {
  111. const { eventEmitter } = useEventEmitterContextContext()
  112. const initialConfig = {
  113. namespace: 'prompt-editor',
  114. nodes: [
  115. CustomTextNode,
  116. {
  117. replace: TextNode,
  118. with: (node: TextNode) => new CustomTextNode(node.__text),
  119. },
  120. ContextBlockNode,
  121. HistoryBlockNode,
  122. QueryBlockNode,
  123. VariableValueBlockNode,
  124. ],
  125. editorState: value ? textToEditorState(value as string) : null,
  126. onError: (error: Error) => {
  127. throw error
  128. },
  129. }
  130. const handleEditorChange = (editorState: EditorState) => {
  131. const text = editorState.read(() => $getRoot().getTextContent())
  132. if (onChange)
  133. onChange(text.replaceAll('\n\n', '\n'))
  134. }
  135. useEffect(() => {
  136. eventEmitter?.emit({
  137. type: UPDATE_DATASETS_EVENT_EMITTER,
  138. payload: contextBlock.datasets,
  139. } as any)
  140. }, [eventEmitter, contextBlock.datasets])
  141. useEffect(() => {
  142. eventEmitter?.emit({
  143. type: UPDATE_HISTORY_EVENT_EMITTER,
  144. payload: historyBlock.history,
  145. } as any)
  146. }, [eventEmitter, historyBlock.history])
  147. return (
  148. <LexicalComposer initialConfig={{ ...initialConfig, editable }}>
  149. <div className='relative'>
  150. <RichTextPlugin
  151. contentEditable={<ContentEditable className={`${className} outline-none text-sm text-gray-700 leading-6`} />}
  152. placeholder={<Placeholder />}
  153. ErrorBoundary={LexicalErrorBoundary}
  154. />
  155. <ComponentPicker
  156. contextDisabled={!contextBlock.selectable}
  157. contextShow={contextBlock.show}
  158. historyDisabled={!historyBlock.selectable}
  159. historyShow={historyBlock.show}
  160. queryDisabled={!queryBlock.selectable}
  161. queryShow={queryBlock.show}
  162. />
  163. <VariablePicker items={variableBlock.variables} />
  164. {
  165. contextBlock.show && (
  166. <>
  167. <ContextBlock
  168. datasets={contextBlock.datasets}
  169. onAddContext={contextBlock.onAddContext}
  170. onInsert={contextBlock.onInsert}
  171. onDelete={contextBlock.onDelete}
  172. />
  173. <ContextBlockReplacementBlock
  174. datasets={contextBlock.datasets}
  175. onAddContext={contextBlock.onAddContext}
  176. onInsert={contextBlock.onInsert}
  177. />
  178. </>
  179. )
  180. }
  181. <VariableBlock />
  182. {
  183. historyBlock.show && (
  184. <>
  185. <HistoryBlock
  186. roleName={historyBlock.history}
  187. onEditRole={historyBlock.onEditRole}
  188. onInsert={historyBlock.onInsert}
  189. onDelete={historyBlock.onDelete}
  190. />
  191. <HistoryBlockReplacementBlock
  192. roleName={historyBlock.history}
  193. onEditRole={historyBlock.onEditRole}
  194. onInsert={historyBlock.onInsert}
  195. />
  196. </>
  197. )
  198. }
  199. {
  200. queryBlock.show && (
  201. <>
  202. <QueryBlock
  203. onInsert={queryBlock.onInsert}
  204. onDelete={queryBlock.onDelete}
  205. />
  206. <QueryBlockReplacementBlock />
  207. </>
  208. )
  209. }
  210. <VariableValueBlock />
  211. <OnChangePlugin onChange={handleEditorChange} />
  212. <OnBlurBlock onBlur={onBlur} />
  213. {/* <TreeView /> */}
  214. </div>
  215. </LexicalComposer>
  216. )
  217. }
  218. export default PromptEditor