config-prompt.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import produce from 'immer'
  6. import { ReactSortable } from 'react-sortablejs'
  7. import { v4 as uuid4 } from 'uuid'
  8. import type { PromptItem, ValueSelector, Var, Variable } from '../../../types'
  9. import { EditionType, PromptRole } from '../../../types'
  10. import useAvailableVarList from '../../_base/hooks/use-available-var-list'
  11. import { useWorkflowStore } from '../../../store'
  12. import ConfigPromptItem from './config-prompt-item'
  13. import cn from '@/utils/classnames'
  14. import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
  15. import AddButton from '@/app/components/workflow/nodes/_base/components/add-button'
  16. import { DragHandle } from '@/app/components/base/icons/src/vender/line/others'
  17. const i18nPrefix = 'workflow.nodes.llm'
  18. type Props = {
  19. readOnly: boolean
  20. nodeId: string
  21. filterVar: (payload: Var, selector: ValueSelector) => boolean
  22. isChatModel: boolean
  23. isChatApp: boolean
  24. payload: PromptItem | PromptItem[]
  25. onChange: (payload: PromptItem | PromptItem[]) => void
  26. isShowContext: boolean
  27. hasSetBlockStatus: {
  28. context: boolean
  29. history: boolean
  30. query: boolean
  31. }
  32. varList?: Variable[]
  33. handleAddVariable: (payload: any) => void
  34. }
  35. const ConfigPrompt: FC<Props> = ({
  36. readOnly,
  37. nodeId,
  38. filterVar,
  39. isChatModel,
  40. isChatApp,
  41. payload,
  42. onChange,
  43. isShowContext,
  44. hasSetBlockStatus,
  45. varList = [],
  46. handleAddVariable,
  47. }) => {
  48. const { t } = useTranslation()
  49. const workflowStore = useWorkflowStore()
  50. const {
  51. setControlPromptEditorRerenderKey,
  52. } = workflowStore.getState()
  53. const payloadWithIds = (isChatModel && Array.isArray(payload))
  54. ? payload.map((item) => {
  55. const id = uuid4()
  56. return {
  57. id: item.id || id,
  58. p: {
  59. ...item,
  60. id: item.id || id,
  61. },
  62. }
  63. })
  64. : []
  65. const {
  66. availableVars,
  67. availableNodesWithParent,
  68. } = useAvailableVarList(nodeId, {
  69. onlyLeafNodeVar: false,
  70. filterVar,
  71. })
  72. const handleChatModePromptChange = useCallback((index: number) => {
  73. return (prompt: string) => {
  74. const newPrompt = produce(payload as PromptItem[], (draft) => {
  75. draft[index][draft[index].edition_type === EditionType.jinja2 ? 'jinja2_text' : 'text'] = prompt
  76. })
  77. onChange(newPrompt)
  78. }
  79. }, [onChange, payload])
  80. const handleChatModeEditionTypeChange = useCallback((index: number) => {
  81. return (editionType: EditionType) => {
  82. const newPrompt = produce(payload as PromptItem[], (draft) => {
  83. draft[index].edition_type = editionType
  84. })
  85. onChange(newPrompt)
  86. }
  87. }, [onChange, payload])
  88. const handleChatModeMessageRoleChange = useCallback((index: number) => {
  89. return (role: PromptRole) => {
  90. const newPrompt = produce(payload as PromptItem[], (draft) => {
  91. draft[index].role = role
  92. })
  93. onChange(newPrompt)
  94. }
  95. }, [onChange, payload])
  96. const handleAddPrompt = useCallback(() => {
  97. const newPrompt = produce(payload as PromptItem[], (draft) => {
  98. if (draft.length === 0) {
  99. draft.push({ role: PromptRole.system, text: '' })
  100. return
  101. }
  102. const isLastItemUser = draft[draft.length - 1].role === PromptRole.user
  103. draft.push({ role: isLastItemUser ? PromptRole.assistant : PromptRole.user, text: '' })
  104. })
  105. onChange(newPrompt)
  106. }, [onChange, payload])
  107. const handleRemove = useCallback((index: number) => {
  108. return () => {
  109. const newPrompt = produce(payload as PromptItem[], (draft) => {
  110. draft.splice(index, 1)
  111. })
  112. onChange(newPrompt)
  113. }
  114. }, [onChange, payload])
  115. const handleCompletionPromptChange = useCallback((prompt: string) => {
  116. const newPrompt = produce(payload as PromptItem, (draft) => {
  117. draft[draft.edition_type === EditionType.jinja2 ? 'jinja2_text' : 'text'] = prompt
  118. })
  119. onChange(newPrompt)
  120. }, [onChange, payload])
  121. const handleGenerated = useCallback((prompt: string) => {
  122. handleCompletionPromptChange(prompt)
  123. setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
  124. }, [handleCompletionPromptChange, setControlPromptEditorRerenderKey])
  125. const handleCompletionEditionTypeChange = useCallback((editionType: EditionType) => {
  126. const newPrompt = produce(payload as PromptItem, (draft) => {
  127. draft.edition_type = editionType
  128. })
  129. onChange(newPrompt)
  130. }, [onChange, payload])
  131. const canChooseSystemRole = (() => {
  132. if (isChatModel && Array.isArray(payload))
  133. return !payload.find(item => item.role === PromptRole.system)
  134. return false
  135. })()
  136. return (
  137. <div>
  138. {(isChatModel && Array.isArray(payload))
  139. ? (
  140. <div>
  141. <div className='space-y-2'>
  142. <ReactSortable className="space-y-1"
  143. list={payloadWithIds}
  144. setList={(list) => {
  145. if ((payload as PromptItem[])?.[0]?.role === PromptRole.system && list[0].p?.role !== PromptRole.system)
  146. return
  147. onChange(list.map(item => item.p))
  148. }}
  149. handle='.handle'
  150. ghostClass="opacity-50"
  151. animation={150}
  152. >
  153. {
  154. (payload as PromptItem[]).map((item, index) => {
  155. const canDrag = (() => {
  156. if (readOnly)
  157. return false
  158. if (index === 0 && item.role === PromptRole.system)
  159. return false
  160. return true
  161. })()
  162. return (
  163. <div key={item.id || index} className='relative group'>
  164. {canDrag && <DragHandle className='group-hover:block hidden absolute left-[-14px] top-2 w-3.5 h-3.5 text-gray-400' />}
  165. <ConfigPromptItem
  166. className={cn(canDrag && 'handle')}
  167. headerClassName={cn(canDrag && 'cursor-grab')}
  168. canNotChooseSystemRole={!canChooseSystemRole}
  169. canRemove={payload.length > 1 && !(index === 0 && item.role === PromptRole.system)}
  170. readOnly={readOnly}
  171. id={item.id!}
  172. handleChatModeMessageRoleChange={handleChatModeMessageRoleChange(index)}
  173. isChatModel={isChatModel}
  174. isChatApp={isChatApp}
  175. payload={item}
  176. onPromptChange={handleChatModePromptChange(index)}
  177. onEditionTypeChange={handleChatModeEditionTypeChange(index)}
  178. onRemove={handleRemove(index)}
  179. isShowContext={isShowContext}
  180. hasSetBlockStatus={hasSetBlockStatus}
  181. availableVars={availableVars}
  182. availableNodes={availableNodesWithParent}
  183. varList={varList}
  184. handleAddVariable={handleAddVariable}
  185. />
  186. </div>
  187. )
  188. })
  189. }
  190. </ReactSortable>
  191. </div>
  192. <AddButton
  193. className='mt-2'
  194. text={t(`${i18nPrefix}.addMessage`)}
  195. onClick={handleAddPrompt}
  196. />
  197. </div>
  198. )
  199. : (
  200. <div>
  201. <Editor
  202. instanceId={`${nodeId}-chat-workflow-llm-prompt-editor`}
  203. title={<span className='capitalize'>{t(`${i18nPrefix}.prompt`)}</span>}
  204. value={((payload as PromptItem).edition_type === EditionType.basic || !(payload as PromptItem).edition_type) ? (payload as PromptItem).text : ((payload as PromptItem).jinja2_text || '')}
  205. onChange={handleCompletionPromptChange}
  206. readOnly={readOnly}
  207. isChatModel={isChatModel}
  208. isChatApp={isChatApp}
  209. isShowContext={isShowContext}
  210. hasSetBlockStatus={hasSetBlockStatus}
  211. nodesOutputVars={availableVars}
  212. availableNodes={availableNodesWithParent}
  213. isSupportPromptGenerator
  214. isSupportJinja
  215. editionType={(payload as PromptItem).edition_type}
  216. varList={varList}
  217. onEditionTypeChange={handleCompletionEditionTypeChange}
  218. handleAddVariable={handleAddVariable}
  219. onGenerated={handleGenerated}
  220. />
  221. </div>
  222. )}
  223. </div>
  224. )
  225. }
  226. export default React.memo(ConfigPrompt)