index.tsx 6.9 KB

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