simple-prompt-input.tsx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import { useBoolean } from 'ahooks'
  6. import cn from 'classnames'
  7. import produce from 'immer'
  8. import { useContext } from 'use-context-selector'
  9. import ConfirmAddVar from './confirm-add-var'
  10. import s from './style.module.css'
  11. import PromptEditorHeightResizeWrap from './prompt-editor-height-resize-wrap'
  12. import { type PromptVariable } from '@/models/debug'
  13. import Tooltip from '@/app/components/base/tooltip'
  14. import { AppType } from '@/types/app'
  15. import { getNewVar, getVars } from '@/utils/var'
  16. import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
  17. import AutomaticBtn from '@/app/components/app/configuration/config/automatic/automatic-btn'
  18. import type { AutomaticRes } from '@/service/debug'
  19. import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
  20. import PromptEditor from '@/app/components/base/prompt-editor'
  21. import ConfigContext from '@/context/debug-configuration'
  22. import { useModalContext } from '@/context/modal-context'
  23. import type { ExternalDataTool } from '@/models/common'
  24. import { useToastContext } from '@/app/components/base/toast'
  25. import { useEventEmitterContextContext } from '@/context/event-emitter'
  26. import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var'
  27. import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
  28. import { PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER } from '@/app/components/base/prompt-editor/plugins/update-block'
  29. export type ISimplePromptInput = {
  30. mode: AppType
  31. promptTemplate: string
  32. promptVariables: PromptVariable[]
  33. readonly?: boolean
  34. onChange?: (promp: string, promptVariables: PromptVariable[]) => void
  35. }
  36. const Prompt: FC<ISimplePromptInput> = ({
  37. mode,
  38. promptTemplate,
  39. promptVariables,
  40. readonly = false,
  41. onChange,
  42. }) => {
  43. const { t } = useTranslation()
  44. const { eventEmitter } = useEventEmitterContextContext()
  45. const {
  46. modelConfig,
  47. dataSets,
  48. setModelConfig,
  49. setPrevPromptConfig,
  50. setIntroduction,
  51. hasSetBlockStatus,
  52. showSelectDataSet,
  53. externalDataToolsConfig,
  54. isAgent,
  55. } = useContext(ConfigContext)
  56. const { notify } = useToastContext()
  57. const { setShowExternalDataToolModal } = useModalContext()
  58. const handleOpenExternalDataToolModal = () => {
  59. setShowExternalDataToolModal({
  60. payload: {},
  61. onSaveCallback: (newExternalDataTool: ExternalDataTool) => {
  62. eventEmitter?.emit({
  63. type: ADD_EXTERNAL_DATA_TOOL,
  64. payload: newExternalDataTool,
  65. } as any)
  66. eventEmitter?.emit({
  67. type: INSERT_VARIABLE_VALUE_BLOCK_COMMAND,
  68. payload: newExternalDataTool.variable,
  69. } as any)
  70. },
  71. onValidateBeforeSaveCallback: (newExternalDataTool: ExternalDataTool) => {
  72. for (let i = 0; i < promptVariables.length; i++) {
  73. if (promptVariables[i].key === newExternalDataTool.variable) {
  74. notify({ type: 'error', message: t('appDebug.varKeyError.keyAlreadyExists', { key: promptVariables[i].key }) })
  75. return false
  76. }
  77. }
  78. return true
  79. },
  80. })
  81. }
  82. const promptVariablesObj = (() => {
  83. const obj: Record<string, boolean> = {}
  84. promptVariables.forEach((item) => {
  85. obj[item.key] = true
  86. })
  87. return obj
  88. })()
  89. const [newPromptVariables, setNewPromptVariables] = React.useState<PromptVariable[]>(promptVariables)
  90. const [newTemplates, setNewTemplates] = React.useState('')
  91. const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false)
  92. const handleChange = (newTemplates: string, keys: string[]) => {
  93. const newPromptVariables = keys.filter(key => !(key in promptVariablesObj) && !externalDataToolsConfig.find(item => item.variable === key)).map(key => getNewVar(key, ''))
  94. if (newPromptVariables.length > 0) {
  95. setNewPromptVariables(newPromptVariables)
  96. setNewTemplates(newTemplates)
  97. showConfirmAddVar()
  98. return
  99. }
  100. onChange?.(newTemplates, [])
  101. }
  102. const handleAutoAdd = (isAdd: boolean) => {
  103. return () => {
  104. onChange?.(newTemplates, isAdd ? newPromptVariables : [])
  105. hideConfirmAddVar()
  106. }
  107. }
  108. const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
  109. const handleAutomaticRes = (res: AutomaticRes) => {
  110. const newModelConfig = produce(modelConfig, (draft) => {
  111. draft.configs.prompt_template = res.prompt
  112. draft.configs.prompt_variables = res.variables.map(key => ({ key, name: key, type: 'string', required: true }))
  113. })
  114. setModelConfig(newModelConfig)
  115. setPrevPromptConfig(modelConfig.configs)
  116. if (mode !== AppType.completion)
  117. setIntroduction(res.opening_statement)
  118. showAutomaticFalse()
  119. eventEmitter?.emit({
  120. type: PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER,
  121. payload: res.prompt,
  122. } as any)
  123. }
  124. const minHeight = 228
  125. const [editorHeight, setEditorHeight] = useState(minHeight)
  126. return (
  127. <div className={cn(!readonly ? `${s.gradientBorder}` : 'bg-gray-50', ' relative shadow-md')}>
  128. <div className='rounded-xl bg-[#EEF4FF]'>
  129. <div className="flex justify-between items-center h-11 px-3">
  130. <div className="flex items-center space-x-1">
  131. <div className='h2'>{mode !== AppType.completion ? t('appDebug.chatSubTitle') : t('appDebug.completionSubTitle')}</div>
  132. {!readonly && (
  133. <Tooltip
  134. htmlContent={<div className='w-[180px]'>
  135. {t('appDebug.promptTip')}
  136. </div>}
  137. selector='config-prompt-tooltip'>
  138. <HelpCircle className='w-[14px] h-[14px] text-indigo-400' />
  139. </Tooltip>
  140. )}
  141. </div>
  142. <div className='flex items-center'>
  143. {!isAgent && !readonly && (
  144. <AutomaticBtn onClick={showAutomaticTrue} />
  145. )}
  146. </div>
  147. </div>
  148. <PromptEditorHeightResizeWrap
  149. className='px-4 pt-2 min-h-[228px] bg-white rounded-t-xl text-sm text-gray-700'
  150. height={editorHeight}
  151. minHeight={minHeight}
  152. onHeightChange={setEditorHeight}
  153. footer={(
  154. <div className='pl-4 pb-2 flex bg-white rounded-b-xl'>
  155. <div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{promptTemplate.length}</div>
  156. </div>
  157. )}
  158. >
  159. <PromptEditor
  160. className='min-h-[210px]'
  161. compact
  162. value={promptTemplate}
  163. contextBlock={{
  164. show: false,
  165. selectable: !hasSetBlockStatus.context,
  166. datasets: dataSets.map(item => ({
  167. id: item.id,
  168. name: item.name,
  169. type: item.data_source_type,
  170. })),
  171. onAddContext: showSelectDataSet,
  172. }}
  173. variableBlock={{
  174. show: true,
  175. variables: modelConfig.configs.prompt_variables.filter(item => item.type !== 'api').map(item => ({
  176. name: item.name,
  177. value: item.key,
  178. })),
  179. }}
  180. externalToolBlock={{
  181. show: true,
  182. externalTools: modelConfig.configs.prompt_variables.filter(item => item.type === 'api').map(item => ({
  183. name: item.name,
  184. variableName: item.key,
  185. icon: item.icon,
  186. icon_background: item.icon_background,
  187. })),
  188. onAddExternalTool: handleOpenExternalDataToolModal,
  189. }}
  190. historyBlock={{
  191. show: false,
  192. selectable: false,
  193. history: {
  194. user: '',
  195. assistant: '',
  196. },
  197. onEditRole: () => { },
  198. }}
  199. queryBlock={{
  200. show: false,
  201. selectable: !hasSetBlockStatus.query,
  202. }}
  203. onChange={(value) => {
  204. handleChange?.(value, [])
  205. }}
  206. onBlur={() => {
  207. handleChange(promptTemplate, getVars(promptTemplate))
  208. }}
  209. />
  210. </PromptEditorHeightResizeWrap>
  211. </div>
  212. {isShowConfirmAddVar && (
  213. <ConfirmAddVar
  214. varNameArr={newPromptVariables.map(v => v.name)}
  215. onConfrim={handleAutoAdd(true)}
  216. onCancel={handleAutoAdd(false)}
  217. onHide={hideConfirmAddVar}
  218. />
  219. )}
  220. {showAutomatic && (
  221. <GetAutomaticResModal
  222. mode={mode as AppType}
  223. isShow={showAutomatic}
  224. onClose={showAutomaticFalse}
  225. onFinished={handleAutomaticRes}
  226. />
  227. )}
  228. </div>
  229. )
  230. }
  231. export default React.memo(Prompt)