modal.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import React, { useCallback, useEffect, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import { useBoolean } from 'ahooks'
  4. import produce from 'immer'
  5. import { ReactSortable } from 'react-sortablejs'
  6. import { RiAddLine, RiAsterisk, RiCloseLine, RiDeleteBinLine, RiDraggable } from '@remixicon/react'
  7. import Modal from '@/app/components/base/modal'
  8. import Button from '@/app/components/base/button'
  9. import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var'
  10. import type { OpeningStatement } from '@/app/components/base/features/types'
  11. import { getInputKeys } from '@/app/components/base/block-input'
  12. import type { PromptVariable } from '@/models/debug'
  13. import type { InputVar } from '@/app/components/workflow/types'
  14. import { getNewVar } from '@/utils/var'
  15. type OpeningSettingModalProps = {
  16. data: OpeningStatement
  17. onSave: (newState: OpeningStatement) => void
  18. onCancel: () => void
  19. promptVariables?: PromptVariable[]
  20. workflowVariables?: InputVar[]
  21. onAutoAddPromptVariable?: (variable: PromptVariable[]) => void
  22. }
  23. const MAX_QUESTION_NUM = 10
  24. const OpeningSettingModal = ({
  25. data,
  26. onSave,
  27. onCancel,
  28. promptVariables = [],
  29. workflowVariables = [],
  30. onAutoAddPromptVariable,
  31. }: OpeningSettingModalProps) => {
  32. const { t } = useTranslation()
  33. const [tempValue, setTempValue] = useState(data?.opening_statement || '')
  34. useEffect(() => {
  35. setTempValue(data.opening_statement || '')
  36. }, [data.opening_statement])
  37. const [tempSuggestedQuestions, setTempSuggestedQuestions] = useState(data.suggested_questions || [])
  38. const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false)
  39. const [notIncludeKeys, setNotIncludeKeys] = useState<string[]>([])
  40. const handleSave = useCallback((ignoreVariablesCheck?: boolean) => {
  41. if (!ignoreVariablesCheck) {
  42. const keys = getInputKeys(tempValue)
  43. const promptKeys = promptVariables.map(item => item.key)
  44. const workflowVariableKeys = workflowVariables.map(item => item.variable)
  45. let notIncludeKeys: string[] = []
  46. if (promptKeys.length === 0 && workflowVariables.length === 0) {
  47. if (keys.length > 0)
  48. notIncludeKeys = keys
  49. }
  50. else {
  51. if (workflowVariables.length > 0)
  52. notIncludeKeys = keys.filter(key => !workflowVariableKeys.includes(key))
  53. else notIncludeKeys = keys.filter(key => !promptKeys.includes(key))
  54. }
  55. if (notIncludeKeys.length > 0) {
  56. setNotIncludeKeys(notIncludeKeys)
  57. showConfirmAddVar()
  58. return
  59. }
  60. }
  61. const newOpening = produce(data, (draft) => {
  62. if (draft) {
  63. draft.opening_statement = tempValue
  64. draft.suggested_questions = tempSuggestedQuestions
  65. }
  66. })
  67. onSave(newOpening)
  68. }, [data, onSave, promptVariables, workflowVariables, showConfirmAddVar, tempSuggestedQuestions, tempValue])
  69. const cancelAutoAddVar = useCallback(() => {
  70. hideConfirmAddVar()
  71. handleSave(true)
  72. }, [handleSave, hideConfirmAddVar])
  73. const autoAddVar = useCallback(() => {
  74. onAutoAddPromptVariable?.([
  75. ...notIncludeKeys.map(key => getNewVar(key, 'string')),
  76. ])
  77. hideConfirmAddVar()
  78. handleSave(true)
  79. }, [handleSave, hideConfirmAddVar, notIncludeKeys, onAutoAddPromptVariable])
  80. const renderQuestions = () => {
  81. return (
  82. <div>
  83. <div className='flex items-center py-2'>
  84. <div className='shrink-0 flex space-x-0.5 leading-[18px] text-xs font-medium text-gray-500'>
  85. <div className='uppercase'>{t('appDebug.openingStatement.openingQuestion')}</div>
  86. <div>·</div>
  87. <div>{tempSuggestedQuestions.length}/{MAX_QUESTION_NUM}</div>
  88. </div>
  89. <div className='ml-3 grow w-0 h-px bg-[#243, 244, 246]'></div>
  90. </div>
  91. <ReactSortable
  92. className="space-y-1"
  93. list={tempSuggestedQuestions.map((name, index) => {
  94. return {
  95. id: index,
  96. name,
  97. }
  98. })}
  99. setList={list => setTempSuggestedQuestions(list.map(item => item.name))}
  100. handle='.handle'
  101. ghostClass="opacity-50"
  102. animation={150}
  103. >
  104. {tempSuggestedQuestions.map((question, index) => {
  105. return (
  106. <div className='group relative rounded-lg border border-gray-200 flex items-center pl-2.5 hover:border-gray-300 hover:bg-white' key={index}>
  107. <RiDraggable className='handle w-4 h-4 cursor-grab' />
  108. <input
  109. type="input"
  110. value={question || ''}
  111. onChange={(e) => {
  112. const value = e.target.value
  113. setTempSuggestedQuestions(tempSuggestedQuestions.map((item, i) => {
  114. if (index === i)
  115. return value
  116. return item
  117. }))
  118. }}
  119. className={'w-full overflow-x-auto pl-1.5 pr-8 text-sm leading-9 text-gray-900 border-0 grow h-9 bg-transparent focus:outline-none cursor-pointer rounded-lg'}
  120. />
  121. <div
  122. className='block absolute top-1/2 translate-y-[-50%] right-1.5 p-1 rounded-md cursor-pointer hover:bg-[#FEE4E2] hover:text-[#D92D20]'
  123. onClick={() => {
  124. setTempSuggestedQuestions(tempSuggestedQuestions.filter((_, i) => index !== i))
  125. }}
  126. >
  127. <RiDeleteBinLine className='w-3.5 h-3.5' />
  128. </div>
  129. </div>
  130. )
  131. })}</ReactSortable>
  132. {tempSuggestedQuestions.length < MAX_QUESTION_NUM && (
  133. <div
  134. onClick={() => { setTempSuggestedQuestions([...tempSuggestedQuestions, '']) }}
  135. className='mt-1 flex items-center h-9 px-3 gap-2 rounded-lg cursor-pointer text-gray-400 bg-gray-100 hover:bg-gray-200'>
  136. <RiAddLine className='w-4 h-4' />
  137. <div className='text-gray-500 text-[13px]'>{t('appDebug.variableConfig.addOption')}</div>
  138. </div>
  139. )}
  140. </div>
  141. )
  142. }
  143. return (
  144. <Modal
  145. isShow
  146. onClose={() => { }}
  147. className='!p-6 !mt-14 !max-w-none !w-[640px] !bg-components-panel-bg-blur'
  148. >
  149. <div className='flex items-center justify-between mb-6'>
  150. <div className='text-text-primary title-2xl-semi-bold'>{t('appDebug.feature.conversationOpener.title')}</div>
  151. <div className='p-1 cursor-pointer' onClick={onCancel}><RiCloseLine className='w-4 h-4 text-text-tertiary'/></div>
  152. </div>
  153. <div className='flex gap-2 mb-8'>
  154. <div className='shrink-0 mt-1.5 w-8 h-8 p-1.5 rounded-lg border-components-panel-border bg-util-colors-orange-dark-orange-dark-500'>
  155. <RiAsterisk className='w-5 h-5 text-text-primary-on-surface' />
  156. </div>
  157. <div className='grow p-3 bg-chat-bubble-bg rounded-2xl border-t border-divider-subtle shadow-xs'>
  158. <textarea
  159. value={tempValue}
  160. rows={3}
  161. onChange={e => setTempValue(e.target.value)}
  162. className="w-full px-0 text-text-secondary system-md-regular border-0 bg-transparent focus:outline-none"
  163. placeholder={t('appDebug.openingStatement.placeholder') as string}
  164. />
  165. {renderQuestions()}
  166. </div>
  167. </div>
  168. <div className='flex items-center justify-end'>
  169. <Button
  170. onClick={onCancel}
  171. className='mr-2'
  172. >
  173. {t('common.operation.cancel')}
  174. </Button>
  175. <Button
  176. variant='primary'
  177. onClick={() => handleSave()}
  178. >
  179. {t('common.operation.save')}
  180. </Button>
  181. </div>
  182. {isShowConfirmAddVar && (
  183. <ConfirmAddVar
  184. varNameArr={notIncludeKeys}
  185. onConfirm={autoAddVar}
  186. onCancel={cancelAutoAddVar}
  187. onHide={hideConfirmAddVar}
  188. />
  189. )}
  190. </Modal>
  191. )
  192. }
  193. export default OpeningSettingModal