123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- import React, { useCallback, useEffect, useState } from 'react'
- import { useTranslation } from 'react-i18next'
- import { useBoolean } from 'ahooks'
- import produce from 'immer'
- import { ReactSortable } from 'react-sortablejs'
- import { RiAddLine, RiAsterisk, RiCloseLine, RiDeleteBinLine, RiDraggable } from '@remixicon/react'
- import Modal from '@/app/components/base/modal'
- import Button from '@/app/components/base/button'
- import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var'
- import type { OpeningStatement } from '@/app/components/base/features/types'
- import { getInputKeys } from '@/app/components/base/block-input'
- import type { PromptVariable } from '@/models/debug'
- import type { InputVar } from '@/app/components/workflow/types'
- import { getNewVar } from '@/utils/var'
- type OpeningSettingModalProps = {
- data: OpeningStatement
- onSave: (newState: OpeningStatement) => void
- onCancel: () => void
- promptVariables?: PromptVariable[]
- workflowVariables?: InputVar[]
- onAutoAddPromptVariable?: (variable: PromptVariable[]) => void
- }
- const MAX_QUESTION_NUM = 10
- const OpeningSettingModal = ({
- data,
- onSave,
- onCancel,
- promptVariables = [],
- workflowVariables = [],
- onAutoAddPromptVariable,
- }: OpeningSettingModalProps) => {
- const { t } = useTranslation()
- const [tempValue, setTempValue] = useState(data?.opening_statement || '')
- useEffect(() => {
- setTempValue(data.opening_statement || '')
- }, [data.opening_statement])
- const [tempSuggestedQuestions, setTempSuggestedQuestions] = useState(data.suggested_questions || [])
- const [isShowConfirmAddVar, { setTrue: showConfirmAddVar, setFalse: hideConfirmAddVar }] = useBoolean(false)
- const [notIncludeKeys, setNotIncludeKeys] = useState<string[]>([])
- const handleSave = useCallback((ignoreVariablesCheck?: boolean) => {
- if (!ignoreVariablesCheck) {
- const keys = getInputKeys(tempValue)
- const promptKeys = promptVariables.map(item => item.key)
- const workflowVariableKeys = workflowVariables.map(item => item.variable)
- let notIncludeKeys: string[] = []
- if (promptKeys.length === 0 && workflowVariables.length === 0) {
- if (keys.length > 0)
- notIncludeKeys = keys
- }
- else {
- if (workflowVariables.length > 0)
- notIncludeKeys = keys.filter(key => !workflowVariableKeys.includes(key))
- else notIncludeKeys = keys.filter(key => !promptKeys.includes(key))
- }
- if (notIncludeKeys.length > 0) {
- setNotIncludeKeys(notIncludeKeys)
- showConfirmAddVar()
- return
- }
- }
- const newOpening = produce(data, (draft) => {
- if (draft) {
- draft.opening_statement = tempValue
- draft.suggested_questions = tempSuggestedQuestions
- }
- })
- onSave(newOpening)
- }, [data, onSave, promptVariables, workflowVariables, showConfirmAddVar, tempSuggestedQuestions, tempValue])
- const cancelAutoAddVar = useCallback(() => {
- hideConfirmAddVar()
- handleSave(true)
- }, [handleSave, hideConfirmAddVar])
- const autoAddVar = useCallback(() => {
- onAutoAddPromptVariable?.([
- ...notIncludeKeys.map(key => getNewVar(key, 'string')),
- ])
- hideConfirmAddVar()
- handleSave(true)
- }, [handleSave, hideConfirmAddVar, notIncludeKeys, onAutoAddPromptVariable])
- const renderQuestions = () => {
- return (
- <div>
- <div className='flex items-center py-2'>
- <div className='shrink-0 flex space-x-0.5 leading-[18px] text-xs font-medium text-gray-500'>
- <div className='uppercase'>{t('appDebug.openingStatement.openingQuestion')}</div>
- <div>·</div>
- <div>{tempSuggestedQuestions.length}/{MAX_QUESTION_NUM}</div>
- </div>
- <div className='ml-3 grow w-0 h-px bg-[#243, 244, 246]'></div>
- </div>
- <ReactSortable
- className="space-y-1"
- list={tempSuggestedQuestions.map((name, index) => {
- return {
- id: index,
- name,
- }
- })}
- setList={list => setTempSuggestedQuestions(list.map(item => item.name))}
- handle='.handle'
- ghostClass="opacity-50"
- animation={150}
- >
- {tempSuggestedQuestions.map((question, index) => {
- return (
- <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}>
- <RiDraggable className='handle w-4 h-4 cursor-grab' />
- <input
- type="input"
- value={question || ''}
- onChange={(e) => {
- const value = e.target.value
- setTempSuggestedQuestions(tempSuggestedQuestions.map((item, i) => {
- if (index === i)
- return value
- return item
- }))
- }}
- 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'}
- />
- <div
- className='block absolute top-1/2 translate-y-[-50%] right-1.5 p-1 rounded-md cursor-pointer hover:bg-[#FEE4E2] hover:text-[#D92D20]'
- onClick={() => {
- setTempSuggestedQuestions(tempSuggestedQuestions.filter((_, i) => index !== i))
- }}
- >
- <RiDeleteBinLine className='w-3.5 h-3.5' />
- </div>
- </div>
- )
- })}</ReactSortable>
- {tempSuggestedQuestions.length < MAX_QUESTION_NUM && (
- <div
- onClick={() => { setTempSuggestedQuestions([...tempSuggestedQuestions, '']) }}
- 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'>
- <RiAddLine className='w-4 h-4' />
- <div className='text-gray-500 text-[13px]'>{t('appDebug.variableConfig.addOption')}</div>
- </div>
- )}
- </div>
- )
- }
- return (
- <Modal
- isShow
- onClose={() => { }}
- className='!p-6 !mt-14 !max-w-none !w-[640px] !bg-components-panel-bg-blur'
- >
- <div className='flex items-center justify-between mb-6'>
- <div className='text-text-primary title-2xl-semi-bold'>{t('appDebug.feature.conversationOpener.title')}</div>
- <div className='p-1 cursor-pointer' onClick={onCancel}><RiCloseLine className='w-4 h-4 text-text-tertiary'/></div>
- </div>
- <div className='flex gap-2 mb-8'>
- <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'>
- <RiAsterisk className='w-5 h-5 text-text-primary-on-surface' />
- </div>
- <div className='grow p-3 bg-chat-bubble-bg rounded-2xl border-t border-divider-subtle shadow-xs'>
- <textarea
- value={tempValue}
- rows={3}
- onChange={e => setTempValue(e.target.value)}
- className="w-full px-0 text-text-secondary system-md-regular border-0 bg-transparent focus:outline-none"
- placeholder={t('appDebug.openingStatement.placeholder') as string}
- />
- {renderQuestions()}
- </div>
- </div>
- <div className='flex items-center justify-end'>
- <Button
- onClick={onCancel}
- className='mr-2'
- >
- {t('common.operation.cancel')}
- </Button>
- <Button
- variant='primary'
- onClick={() => handleSave()}
- >
- {t('common.operation.save')}
- </Button>
- </div>
- {isShowConfirmAddVar && (
- <ConfirmAddVar
- varNameArr={notIncludeKeys}
- onConfirm={autoAddVar}
- onCancel={cancelAutoAddVar}
- onHide={hideConfirmAddVar}
- />
- )}
- </Modal>
- )
- }
- export default OpeningSettingModal
|