index.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. selectable?: boolean
  51. datasets: Dataset[]
  52. onInsert?: () => void
  53. onDelete?: () => void
  54. onAddContext: () => void
  55. }
  56. variableBlock?: {
  57. selectable?: boolean
  58. variables: Option[]
  59. }
  60. historyBlock?: {
  61. show?: boolean
  62. selectable?: boolean
  63. history: RoleName
  64. onInsert?: () => void
  65. onDelete?: () => void
  66. onEditRole: () => void
  67. }
  68. queryBlock?: {
  69. show?: boolean
  70. selectable?: boolean
  71. onInsert?: () => void
  72. onDelete?: () => void
  73. }
  74. }
  75. const PromptEditor: FC<PromptEditorProps> = ({
  76. className,
  77. value,
  78. editable = true,
  79. onChange,
  80. onBlur,
  81. contextBlock = {
  82. selectable: true,
  83. datasets: [],
  84. onAddContext: () => {},
  85. onInsert: () => {},
  86. onDelete: () => {},
  87. },
  88. historyBlock = {
  89. show: true,
  90. selectable: true,
  91. history: {
  92. user: '',
  93. assistant: '',
  94. },
  95. onEditRole: () => {},
  96. onInsert: () => {},
  97. onDelete: () => {},
  98. },
  99. variableBlock = {
  100. variables: [],
  101. },
  102. queryBlock = {
  103. show: true,
  104. selectable: true,
  105. onInsert: () => {},
  106. onDelete: () => {},
  107. },
  108. }) => {
  109. const { eventEmitter } = useEventEmitterContextContext()
  110. const initialConfig = {
  111. namespace: 'prompt-editor',
  112. nodes: [
  113. CustomTextNode,
  114. {
  115. replace: TextNode,
  116. with: (node: TextNode) => new CustomTextNode(node.__text),
  117. },
  118. ContextBlockNode,
  119. HistoryBlockNode,
  120. QueryBlockNode,
  121. VariableValueBlockNode,
  122. ],
  123. editorState: value ? textToEditorState(value as string) : null,
  124. onError: (error: Error) => {
  125. throw error
  126. },
  127. }
  128. const handleEditorChange = (editorState: EditorState) => {
  129. const text = editorState.read(() => $getRoot().getTextContent())
  130. if (onChange)
  131. onChange(text.replaceAll('\n\n', '\n'))
  132. }
  133. useEffect(() => {
  134. eventEmitter?.emit({
  135. type: UPDATE_DATASETS_EVENT_EMITTER,
  136. payload: contextBlock.datasets,
  137. } as any)
  138. }, [eventEmitter, contextBlock.datasets])
  139. useEffect(() => {
  140. eventEmitter?.emit({
  141. type: UPDATE_HISTORY_EVENT_EMITTER,
  142. payload: historyBlock.history,
  143. } as any)
  144. }, [eventEmitter, historyBlock.history])
  145. return (
  146. <LexicalComposer initialConfig={{ ...initialConfig, editable }}>
  147. <div className='relative'>
  148. <RichTextPlugin
  149. contentEditable={<ContentEditable className={`${className} outline-none text-sm text-gray-700 leading-6`} />}
  150. placeholder={<Placeholder />}
  151. ErrorBoundary={LexicalErrorBoundary}
  152. />
  153. <ComponentPicker
  154. contextDisabled={!contextBlock.selectable}
  155. historyDisabled={!historyBlock.selectable}
  156. historyShow={historyBlock.show}
  157. queryDisabled={!queryBlock.selectable}
  158. queryShow={queryBlock.show}
  159. />
  160. <VariablePicker items={variableBlock.variables} />
  161. <ContextBlock
  162. datasets={contextBlock.datasets}
  163. onAddContext={contextBlock.onAddContext}
  164. onInsert={contextBlock.onInsert}
  165. onDelete={contextBlock.onDelete}
  166. />
  167. <ContextBlockReplacementBlock
  168. datasets={contextBlock.datasets}
  169. onAddContext={contextBlock.onAddContext}
  170. onInsert={contextBlock.onInsert}
  171. />
  172. <VariableBlock />
  173. {
  174. historyBlock.show && (
  175. <>
  176. <HistoryBlock
  177. roleName={historyBlock.history}
  178. onEditRole={historyBlock.onEditRole}
  179. onInsert={historyBlock.onInsert}
  180. onDelete={historyBlock.onDelete}
  181. />
  182. <HistoryBlockReplacementBlock
  183. roleName={historyBlock.history}
  184. onEditRole={historyBlock.onEditRole}
  185. onInsert={historyBlock.onInsert}
  186. />
  187. </>
  188. )
  189. }
  190. {
  191. queryBlock.show && (
  192. <>
  193. <QueryBlock
  194. onInsert={queryBlock.onInsert}
  195. onDelete={queryBlock.onDelete}
  196. />
  197. <QueryBlockReplacementBlock />
  198. </>
  199. )
  200. }
  201. <VariableValueBlock />
  202. <OnChangePlugin onChange={handleEditorChange} />
  203. <OnBlurBlock onBlur={onBlur} />
  204. {/* <TreeView /> */}
  205. </div>
  206. </LexicalComposer>
  207. )
  208. }
  209. export default PromptEditor