new style of user inputs

This commit is contained in:
JzoNg 2024-08-16 17:47:47 +08:00
parent 4554ac3ef8
commit 0eb442f954
9 changed files with 170 additions and 51 deletions

View File

@ -0,0 +1,109 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import ConfigContext from '@/context/debug-configuration'
import Input from '@/app/components/base/input'
import Select from '@/app/components/base/select'
import Textarea from '@/app/components/base/textarea'
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import type { Inputs } from '@/models/debug'
import cn from '@/utils/classnames'
type Props = {
inputs: Inputs
}
const ChatUserInput = ({
inputs,
}: Props) => {
const { t } = useTranslation()
const { modelConfig, setInputs } = useContext(ConfigContext)
const promptVariables = modelConfig.configs.prompt_variables.filter(({ key, name }) => {
return key && key?.trim() && name && name?.trim()
})
const promptVariableObj = (() => {
const obj: Record<string, boolean> = {}
promptVariables.forEach((input) => {
obj[input.key] = true
})
return obj
})()
const handleInputValueChange = (key: string, value: string) => {
if (!(key in promptVariableObj))
return
const newInputs = { ...inputs }
promptVariables.forEach((input) => {
if (input.key === key)
newInputs[key] = value
})
setInputs(newInputs)
}
if (!promptVariables.length)
return null
return (
<div className={cn('bg-components-panel-on-panel-item-bg rounded-xl border-[0.5px] border-components-panel-border-subtle shadow-xs z-[1]')}>
<div className='px-4 pt-3 pb-4'>
{promptVariables.map(({ key, name, type, options, max_length, required }, index) => (
<div
key={key}
className='mb-4 last-of-type:mb-0'
>
<div>
<div className='h-6 mb-1 flex items-center gap-1 text-text-secondary system-sm-semibold'>
<div className='truncate'>{name || key}</div>
{!required && <span className='text-text-tertiary system-xs-regular'>{t('workflow.panel.optional')}</span>}
</div>
<div className='grow'>
{type === 'string' && (
<Input
value={inputs[key] ? `${inputs[key]}` : ''}
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
placeholder={name}
autoFocus={index === 0}
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
/>
)}
{type === 'paragraph' && (
<Textarea
className='grow h-[120px]'
placeholder={name}
value={inputs[key] ? `${inputs[key]}` : ''}
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
/>
)}
{type === 'select' && (
<Select
className='w-full'
defaultValue={inputs[key] as string}
onSelect={(i) => { handleInputValueChange(key, i.value as string) }}
items={(options || []).map(i => ({ name: i, value: i }))}
allowSearch={false}
bgClassName='bg-gray-50'
/>
)}
{type === 'number' && (
<Input
type='number'
value={inputs[key] ? `${inputs[key]}` : ''}
onChange={(e) => { handleInputValueChange(key, e.target.value) }}
placeholder={name}
autoFocus={index === 0}
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
/>
)}
</div>
</div>
</div>
))}
</div>
</div>
)
}
export default ChatUserInput

View File

@ -119,8 +119,8 @@ const DebugWithSingleModel = forwardRef<DebugWithSingleModelRefType, DebugWithSi
config={config}
chatList={chatList}
isResponding={isResponding}
chatContainerClassName='p-6'
chatFooterClassName='px-6 pt-10 pb-4'
chatContainerClassName='px-3 pt-6'
chatFooterClassName='px-3 pt-10 pb-2'
suggestedQuestions={suggestedQuestions}
onSend={doSend}
onStopResponding={handleStop}

View File

@ -7,6 +7,7 @@ import { setAutoFreeze } from 'immer'
import { useBoolean } from 'ahooks'
import {
RiAddLine,
RiEqualizer2Line,
} from '@remixicon/react'
import { useContext } from 'use-context-selector'
import { useShallow } from 'zustand/react/shallow'
@ -23,11 +24,15 @@ import {
APP_CHAT_WITH_MULTIPLE_MODEL_RESTART,
} from './types'
import { AppType, ModelModeType, TransferMethod } from '@/types/app'
import ChatUserInput from '@/app/components/app/configuration/debug/chat-user-input'
import PromptValuePanel from '@/app/components/app/configuration/prompt-value-panel'
import ConfigContext from '@/context/debug-configuration'
import { ToastContext } from '@/app/components/base/toast'
import { sendCompletionMessage } from '@/service/debug'
import Button from '@/app/components/base/button'
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
import TooltipPlus from '@/app/components/base/tooltip-plus'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import type { ModelConfig as BackendModelConfig, VisionFile } from '@/types/app'
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
import TextGeneration from '@/app/components/app/text-generate/item'
@ -391,49 +396,71 @@ const Debug: FC<IDebug> = ({
adjustModalWidth()
}, [])
const [expanded, setExpanded] = useState(true)
return (
<>
<div className="shrink-0 pt-4 px-6">
<div className='flex items-center justify-between mb-2'>
<div className='h2 '>{t('appDebug.inputs.title')}</div>
<div className="shrink-0">
<div className='flex items-center justify-between px-4 pt-3 pb-2'>
<div className='text-text-primary system-xl-semibold'>{t('appDebug.inputs.title')}</div>
<div className='flex items-center'>
{
debugWithMultipleModel
? (
<>
<Button
variant='secondary-accent'
variant='ghost-accent'
onClick={() => onMultipleModelConfigsChange(true, [...multipleModelConfigs, { id: `${Date.now()}`, model: '', provider: '', parameters: {} }])}
disabled={multipleModelConfigs.length >= 4}
>
<RiAddLine className='mr-1 w-3.5 h-3.5' />
{t('common.modelProvider.addModel')}({multipleModelConfigs.length}/4)
</Button>
<div className='mx-2 w-[1px] h-[14px] bg-gray-200' />
<div className='mx-2 w-[1px] h-[14px] bg-divider-regular' />
</>
)
: null
}
{mode !== AppType.completion && (
<Button variant='secondary-accent' className='gap-1' onClick={clearConversation}>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.66663 2.66629V5.99963H3.05463M3.05463 5.99963C3.49719 4.90505 4.29041 3.98823 5.30998 3.39287C6.32954 2.7975 7.51783 2.55724 8.68861 2.70972C9.85938 2.8622 10.9465 3.39882 11.7795 4.23548C12.6126 5.07213 13.1445 6.16154 13.292 7.33296M3.05463 5.99963H5.99996M13.3333 13.333V9.99963H12.946M12.946 9.99963C12.5028 11.0936 11.7093 12.0097 10.6898 12.6045C9.67038 13.1993 8.48245 13.4393 7.31203 13.2869C6.1416 13.1344 5.05476 12.5982 4.22165 11.7621C3.38854 10.926 2.8562 9.83726 2.70796 8.66629M12.946 9.99963H9.99996" stroke="#1C64F2" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<span className='text-primary-600 text-[13px] font-semibold'>{t('common.operation.refresh')}</span>
</Button>
<>
<TooltipPlus
popupContent={t('common.operation.refresh')}
>
<ActionButton onClick={clearConversation}>
<RefreshCcw01 className='w-4 h-4' />
</ActionButton>
</TooltipPlus>
<div className='relative ml-1 mr-2'>
<TooltipPlus
popupContent={t('workflow.panel.userInputField')}
>
<ActionButton state={expanded ? ActionButtonState.Active : undefined} onClick={() => setExpanded(!expanded)}>
<RiEqualizer2Line className='w-4 h-4' />
</ActionButton>
</TooltipPlus>
{expanded && <div className='absolute z-10 bottom-[-18px] right-[5px] w-3 h-3 bg-components-panel-on-panel-item-bg border-l-[0.5px] border-t-[0.5px] border-components-panel-border-subtle rotate-45'/>}
</div>
</>
)}
</div>
</div>
<PromptValuePanel
appType={mode as AppType}
onSend={handleSendTextCompletion}
inputs={inputs}
visionConfig={{
...visionConfig,
image_file_size_limit: fileUploadConfigResponse?.image_file_size_limit,
}}
onVisionFilesChange={setCompletionFiles}
/>
{mode !== AppType.completion && expanded && (
<div className='mx-3 mt-1'>
<ChatUserInput inputs={inputs} />
</div>
)}
{mode === AppType.completion && (
<PromptValuePanel
appType={mode as AppType}
onSend={handleSendTextCompletion}
inputs={inputs}
visionConfig={{
...visionConfig,
image_file_size_limit: fileUploadConfigResponse?.image_file_size_limit,
}}
onVisionFilesChange={setCompletionFiles}
/>
)}
</div>
{
debugWithMultipleModel && (
@ -481,7 +508,7 @@ const Debug: FC<IDebug> = ({
)}
{/* Text Generation */}
{mode === AppType.completion && (
<div className="mt-6 px-6 pb-4">
<div className="mt-6 px-3 pb-4">
<GroupName name={t('appDebug.result')} />
{(completionRes || isResponding) && (
<TextGeneration

View File

@ -858,7 +858,7 @@ const Configuration: FC = () => {
<Config />
</div>
{!isMobile && <div className="relative flex flex-col w-1/2 h-full overflow-y-auto grow " style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<div className='flex flex-col h-0 border-t border-l grow rounded-tl-2xl bg-gray-50 '>
<div className='grow flex flex-col h-0 border-t-[0.5px] border-l-[0.5px] rounded-tl-2xl border-components-panel-border bg-chatbot-bg '>
<Debug
isAPIKeySet={isAPIKeySet}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}

View File

@ -222,18 +222,6 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
export default React.memo(PromptValuePanel)
function replaceStringWithValuesWithFormat(str: string, promptVariables: PromptVariable[], inputs: Record<string, any>) {
return str.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
const name = inputs[key]
if (name) { // has set value
return `<div class='inline-block px-1 rounded-md text-gray-900' style='background: rgba(16, 24, 40, 0.1)'>${name}</div>`
}
const valueObj: PromptVariable | undefined = promptVariables.find(v => v.key === key)
return `<div class='inline-block px-1 rounded-md text-gray-500' style='background: rgba(16, 24, 40, 0.05)'>${valueObj ? valueObj.name : match}</div>`
})
}
export function replaceStringWithValues(str: string, promptVariables: PromptVariable[], inputs: Record<string, any>) {
return str.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
const name = inputs[key]
@ -245,8 +233,3 @@ export function replaceStringWithValues(str: string, promptVariables: PromptVari
return valueObj ? `{{${valueObj.name}}}` : match
})
}
// \n -> br
function format(str: string) {
return str.replaceAll('\n', '<br>')
}

View File

@ -14,8 +14,8 @@ const AddButton: FC<Props> = ({
onClick,
}) => {
return (
<div className={cn(className, 'p-1 rounded-md cursor-pointer hover:bg-gray-200 select-none')} onClick={onClick}>
<RiAddLine className='w-4 h-4 text-gray-500' />
<div className={cn(className, 'p-1 rounded-md cursor-pointer hover:bg-state-base-hover select-none')} onClick={onClick}>
<RiAddLine className='w-4 h-4 text-text-tertiary' />
</div>
)
}

View File

@ -91,7 +91,7 @@ const FormItem: FC<Props> = ({
const isContext = type === InputVarType.contexts
const isIterator = type === InputVarType.iterator
return (
<div className={`${className}`}>
<div className={cn(className)}>
{!isArrayLikeType && (
<div className='h-6 mb-1 flex items-center gap-1 text-text-secondary system-sm-semibold'>
<div className='truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>
@ -112,8 +112,7 @@ const FormItem: FC<Props> = ({
{
type === InputVarType.number && (
<input
className="w-full px-3 text-sm leading-8 text-gray-900 border-0 rounded-lg grow h-8 bg-gray-50 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200"
<Input
type="number"
value={value || ''}
onChange={e => onChange(e.target.value)}
@ -157,6 +156,7 @@ const FormItem: FC<Props> = ({
)
}
{/* #TODO# file type new form */}
{
type === InputVarType.files && (
<TextGenerationImageUploader
@ -186,7 +186,7 @@ const FormItem: FC<Props> = ({
(value as any).length > 1
? (<RiDeleteBinLine
onClick={handleArrayItemRemove(index)}
className='mr-1 w-3.5 h-3.5 text-gray-500 cursor-pointer'
className='mr-1 w-3.5 h-3.5 text-text-tertiary cursor-pointer'
/>)
: undefined
}
@ -212,7 +212,7 @@ const FormItem: FC<Props> = ({
(value as any).length > 1
? (<RiDeleteBinLine
onClick={handleArrayItemRemove(index)}
className='mr-1 w-3.5 h-3.5 text-gray-500 cursor-pointer'
className='mr-1 w-3.5 h-3.5 text-text-tertiary cursor-pointer'
/>)
: undefined
}

View File

@ -71,7 +71,7 @@ const Form: FC<Props> = ({
<div className={cn(className, 'space-y-2')}>
{label && (
<div className='mb-1 flex items-center justify-between'>
<div className='flex items-center h-6 text-xs font-medium text-gray-500 uppercase'>{label}</div>
<div className='flex items-center h-6 system-xs-medium-uppercase text-text-tertiary'>{label}</div>
{isArrayLikeType && (
<AddButton onClick={handleAddContext} />
)}

View File

@ -410,7 +410,7 @@ const translation = {
},
},
inputs: {
title: 'Debug and Preview',
title: 'Debug & Preview',
noPrompt: 'Try write some prompt in pre-prompt input',
userInputField: 'User Input Field',
noVar: 'Fill in the value of the variable, which will be automatically replaced in the prompt word every time a new session is started.',