index.tsx 6.9 KB

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