You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import { useMemo } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import { $insertNodes } from 'lexical'
  4. import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
  5. import type {
  6. ContextBlockType,
  7. CurrentBlockType,
  8. ErrorMessageBlockType,
  9. ExternalToolBlockType,
  10. HistoryBlockType,
  11. LastRunBlockType,
  12. QueryBlockType,
  13. VariableBlockType,
  14. WorkflowVariableBlockType,
  15. } from '../../types'
  16. import { INSERT_CONTEXT_BLOCK_COMMAND } from '../context-block'
  17. import { INSERT_HISTORY_BLOCK_COMMAND } from '../history-block'
  18. import { INSERT_QUERY_BLOCK_COMMAND } from '../query-block'
  19. import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '../variable-block'
  20. import { $createCustomTextNode } from '../custom-text/node'
  21. import { PromptMenuItem } from './prompt-option'
  22. import { VariableMenuItem } from './variable-option'
  23. import { PickerBlockMenuOption } from './menu'
  24. import { File05 } from '@/app/components/base/icons/src/vender/solid/files'
  25. import {
  26. MessageClockCircle,
  27. Tool03,
  28. } from '@/app/components/base/icons/src/vender/solid/general'
  29. import { BracketsX } from '@/app/components/base/icons/src/vender/line/development'
  30. import { UserEdit02 } from '@/app/components/base/icons/src/vender/solid/users'
  31. import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
  32. import AppIcon from '@/app/components/base/app-icon'
  33. import { VarType } from '@/app/components/workflow/types'
  34. export const usePromptOptions = (
  35. contextBlock?: ContextBlockType,
  36. queryBlock?: QueryBlockType,
  37. historyBlock?: HistoryBlockType,
  38. ) => {
  39. const { t } = useTranslation()
  40. const [editor] = useLexicalComposerContext()
  41. const promptOptions: PickerBlockMenuOption[] = []
  42. if (contextBlock?.show) {
  43. promptOptions.push(new PickerBlockMenuOption({
  44. key: t('common.promptEditor.context.item.title'),
  45. group: 'prompt context',
  46. render: ({ isSelected, onSelect, onSetHighlight }) => {
  47. return <PromptMenuItem
  48. title={t('common.promptEditor.context.item.title')}
  49. icon={<File05 className='h-4 w-4 text-[#6938EF]' />}
  50. disabled={!contextBlock.selectable}
  51. isSelected={isSelected}
  52. onClick={onSelect}
  53. onMouseEnter={onSetHighlight}
  54. />
  55. },
  56. onSelect: () => {
  57. if (!contextBlock?.selectable)
  58. return
  59. editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined)
  60. },
  61. }))
  62. }
  63. if (queryBlock?.show) {
  64. promptOptions.push(
  65. new PickerBlockMenuOption({
  66. key: t('common.promptEditor.query.item.title'),
  67. group: 'prompt query',
  68. render: ({ isSelected, onSelect, onSetHighlight }) => {
  69. return (
  70. <PromptMenuItem
  71. title={t('common.promptEditor.query.item.title')}
  72. icon={<UserEdit02 className='h-4 w-4 text-[#FD853A]' />}
  73. disabled={!queryBlock.selectable}
  74. isSelected={isSelected}
  75. onClick={onSelect}
  76. onMouseEnter={onSetHighlight}
  77. />
  78. )
  79. },
  80. onSelect: () => {
  81. if (!queryBlock?.selectable)
  82. return
  83. editor.dispatchCommand(INSERT_QUERY_BLOCK_COMMAND, undefined)
  84. },
  85. }),
  86. )
  87. }
  88. if (historyBlock?.show) {
  89. promptOptions.push(
  90. new PickerBlockMenuOption({
  91. key: t('common.promptEditor.history.item.title'),
  92. group: 'prompt history',
  93. render: ({ isSelected, onSelect, onSetHighlight }) => {
  94. return (
  95. <PromptMenuItem
  96. title={t('common.promptEditor.history.item.title')}
  97. icon={<MessageClockCircle className='h-4 w-4 text-[#DD2590]' />}
  98. disabled={!historyBlock.selectable
  99. }
  100. isSelected={isSelected}
  101. onClick={onSelect}
  102. onMouseEnter={onSetHighlight}
  103. />
  104. )
  105. },
  106. onSelect: () => {
  107. if (!historyBlock?.selectable)
  108. return
  109. editor.dispatchCommand(INSERT_HISTORY_BLOCK_COMMAND, undefined)
  110. },
  111. }),
  112. )
  113. }
  114. return promptOptions
  115. }
  116. export const useVariableOptions = (
  117. variableBlock?: VariableBlockType,
  118. queryString?: string,
  119. ): PickerBlockMenuOption[] => {
  120. const { t } = useTranslation()
  121. const [editor] = useLexicalComposerContext()
  122. const options = useMemo(() => {
  123. if (!variableBlock?.variables)
  124. return []
  125. const baseOptions = (variableBlock.variables).map((item) => {
  126. return new PickerBlockMenuOption({
  127. key: item.value,
  128. group: 'prompt variable',
  129. render: ({ queryString, isSelected, onSelect, onSetHighlight }) => {
  130. return (
  131. <VariableMenuItem
  132. title={item.value}
  133. icon={<BracketsX className='h-[14px] w-[14px] text-text-accent' />}
  134. queryString={queryString}
  135. isSelected={isSelected}
  136. onClick={onSelect}
  137. onMouseEnter={onSetHighlight}
  138. />
  139. )
  140. },
  141. onSelect: () => {
  142. editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.value}}}`)
  143. },
  144. })
  145. })
  146. if (!queryString)
  147. return baseOptions
  148. const regex = new RegExp(queryString, 'i')
  149. return baseOptions.filter(option => regex.test(option.key))
  150. }, [editor, queryString, variableBlock])
  151. const addOption = useMemo(() => {
  152. return new PickerBlockMenuOption({
  153. key: t('common.promptEditor.variable.modal.add'),
  154. group: 'prompt variable',
  155. render: ({ queryString, isSelected, onSelect, onSetHighlight }) => {
  156. return (
  157. <VariableMenuItem
  158. title={t('common.promptEditor.variable.modal.add')}
  159. icon={<BracketsX className='h-[14px] w-[14px] text-text-accent' />}
  160. queryString={queryString}
  161. isSelected={isSelected}
  162. onClick={onSelect}
  163. onMouseEnter={onSetHighlight}
  164. />
  165. )
  166. },
  167. onSelect: () => {
  168. editor.update(() => {
  169. const prefixNode = $createCustomTextNode('{{')
  170. const suffixNode = $createCustomTextNode('}}')
  171. $insertNodes([prefixNode, suffixNode])
  172. prefixNode.select()
  173. })
  174. },
  175. })
  176. }, [editor, t])
  177. return useMemo(() => {
  178. return variableBlock?.show ? [...options, addOption] : []
  179. }, [options, addOption, variableBlock?.show])
  180. }
  181. export const useExternalToolOptions = (
  182. externalToolBlockType?: ExternalToolBlockType,
  183. queryString?: string,
  184. ) => {
  185. const { t } = useTranslation()
  186. const [editor] = useLexicalComposerContext()
  187. const options = useMemo(() => {
  188. if (!externalToolBlockType?.externalTools)
  189. return []
  190. const baseToolOptions = (externalToolBlockType.externalTools).map((item) => {
  191. return new PickerBlockMenuOption({
  192. key: item.name,
  193. group: 'external tool',
  194. render: ({ queryString, isSelected, onSelect, onSetHighlight }) => {
  195. return (
  196. <VariableMenuItem
  197. title={item.name}
  198. icon={
  199. <AppIcon
  200. className='!h-[14px] !w-[14px]'
  201. icon={item.icon}
  202. background={item.icon_background}
  203. />
  204. }
  205. extraElement={<div className='text-xs text-text-tertiary'>{item.variableName}</div>}
  206. queryString={queryString}
  207. isSelected={isSelected}
  208. onClick={onSelect}
  209. onMouseEnter={onSetHighlight}
  210. />
  211. )
  212. },
  213. onSelect: () => {
  214. editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.variableName}}}`)
  215. },
  216. })
  217. })
  218. if (!queryString)
  219. return baseToolOptions
  220. const regex = new RegExp(queryString, 'i')
  221. return baseToolOptions.filter(option => regex.test(option.key))
  222. }, [editor, queryString, externalToolBlockType])
  223. const addOption = useMemo(() => {
  224. return new PickerBlockMenuOption({
  225. key: t('common.promptEditor.variable.modal.addTool'),
  226. group: 'external tool',
  227. render: ({ queryString, isSelected, onSelect, onSetHighlight }) => {
  228. return (
  229. <VariableMenuItem
  230. title={t('common.promptEditor.variable.modal.addTool')}
  231. icon={<Tool03 className='h-[14px] w-[14px] text-text-accent' />}
  232. extraElement={< ArrowUpRight className='h-3 w-3 text-text-tertiary' />}
  233. queryString={queryString}
  234. isSelected={isSelected}
  235. onClick={onSelect}
  236. onMouseEnter={onSetHighlight}
  237. />
  238. )
  239. },
  240. onSelect: () => {
  241. externalToolBlockType?.onAddExternalTool?.()
  242. },
  243. })
  244. }, [externalToolBlockType, t])
  245. return useMemo(() => {
  246. return externalToolBlockType?.show ? [...options, addOption] : []
  247. }, [options, addOption, externalToolBlockType?.show])
  248. }
  249. export const useOptions = (
  250. contextBlock?: ContextBlockType,
  251. queryBlock?: QueryBlockType,
  252. historyBlock?: HistoryBlockType,
  253. variableBlock?: VariableBlockType,
  254. externalToolBlockType?: ExternalToolBlockType,
  255. workflowVariableBlockType?: WorkflowVariableBlockType,
  256. currentBlockType?: CurrentBlockType,
  257. errorMessageBlockType?: ErrorMessageBlockType,
  258. lastRunBlockType?: LastRunBlockType,
  259. queryString?: string,
  260. ) => {
  261. const promptOptions = usePromptOptions(contextBlock, queryBlock, historyBlock)
  262. const variableOptions = useVariableOptions(variableBlock, queryString)
  263. const externalToolOptions = useExternalToolOptions(externalToolBlockType, queryString)
  264. const workflowVariableOptions = useMemo(() => {
  265. if (!workflowVariableBlockType?.show)
  266. return []
  267. const res = workflowVariableBlockType.variables || []
  268. if(errorMessageBlockType?.show && res.findIndex(v => v.nodeId === 'error_message') === -1) {
  269. res.unshift({
  270. nodeId: 'error_message',
  271. title: 'error_message',
  272. isFlat: true,
  273. vars: [
  274. {
  275. variable: 'error_message',
  276. type: VarType.string,
  277. },
  278. ],
  279. })
  280. }
  281. if(lastRunBlockType?.show && res.findIndex(v => v.nodeId === 'last_run') === -1) {
  282. res.unshift({
  283. nodeId: 'last_run',
  284. title: 'last_run',
  285. isFlat: true,
  286. vars: [
  287. {
  288. variable: 'last_run',
  289. type: VarType.object,
  290. },
  291. ],
  292. })
  293. }
  294. if(currentBlockType?.show && res.findIndex(v => v.nodeId === 'current') === -1) {
  295. const title = currentBlockType.generatorType === 'prompt' ? 'current_prompt' : 'current_code'
  296. res.unshift({
  297. nodeId: 'current',
  298. title,
  299. isFlat: true,
  300. vars: [
  301. {
  302. variable: 'current',
  303. type: VarType.string,
  304. },
  305. ],
  306. })
  307. }
  308. return res
  309. }, [workflowVariableBlockType?.show, workflowVariableBlockType?.variables, errorMessageBlockType?.show, lastRunBlockType?.show, currentBlockType?.show, currentBlockType?.generatorType])
  310. return useMemo(() => {
  311. return {
  312. workflowVariableOptions,
  313. allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions],
  314. }
  315. }, [promptOptions, variableOptions, externalToolOptions, workflowVariableOptions])
  316. }