simple-prompt-input.tsx 8.7 KB

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