index.tsx 6.8 KB

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