config-prompt.tsx 8.7 KB

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