Fix/application configuration preview style (#597)

This commit is contained in:
zxhlyh 2023-07-19 12:41:35 +08:00 committed by GitHub
parent a6af8e5d8f
commit 753e5f1500
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 86 additions and 79 deletions

View File

@ -40,7 +40,7 @@ const Config: FC = () => {
} = useContext(ConfigContext) } = useContext(ConfigContext)
const isChatApp = mode === AppType.chat const isChatApp = mode === AppType.chat
const { data: userInfo } = useSWR({ url: '/info' }, fetchTenantInfo) const { data: userInfo } = useSWR({ url: '/info' }, fetchTenantInfo)
const targetProvider = userInfo?.providers?.find(({ token_is_set, is_valid }) => token_is_set && is_valid) const openaiProvider = userInfo?.providers?.find(({ token_is_set, is_valid, provider_name }) => token_is_set && is_valid && provider_name === 'openai')
const promptTemplate = modelConfig.configs.prompt_template const promptTemplate = modelConfig.configs.prompt_template
const promptVariables = modelConfig.configs.prompt_variables const promptVariables = modelConfig.configs.prompt_variables
@ -92,7 +92,7 @@ const Config: FC = () => {
}, },
}) })
const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && targetProvider?.provider_name === 'openai')) const hasChatConfig = isChatApp && (featureConfig.openingStatement || featureConfig.suggestedQuestionsAfterAnswer || (featureConfig.speechToText && openaiProvider))
const hasToolbox = false const hasToolbox = false
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false) const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
@ -122,7 +122,7 @@ const Config: FC = () => {
isChatApp={isChatApp} isChatApp={isChatApp}
config={featureConfig} config={featureConfig}
onChange={handleFeatureChange} onChange={handleFeatureChange}
showSpeechToTextItem={targetProvider?.provider_name === 'openai'} showSpeechToTextItem={!!openaiProvider}
/> />
)} )}
{showAutomatic && ( {showAutomatic && (

View File

@ -116,22 +116,22 @@ const Debug: FC<IDebug> = ({
} }
const checkCanSend = () => { const checkCanSend = () => {
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required }) => { const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) // compatible with old version }) // compatible with old version
// debugger // debugger
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs[key]) if (!inputs[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput

View File

@ -122,6 +122,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
items={(options || []).map(i => ({ name: i, value: i }))} items={(options || []).map(i => ({ name: i, value: i }))}
allowSearch={false} allowSearch={false}
bgClassName='bg-gray-50' bgClassName='bg-gray-50'
overlayClassName='z-[11]'
/> />
) )
: ( : (

View File

@ -31,6 +31,7 @@ export type ISelectProps = {
allowSearch?: boolean allowSearch?: boolean
bgClassName?: string bgClassName?: string
placeholder?: string placeholder?: string
overlayClassName?: string
} }
const Select: FC<ISelectProps> = ({ const Select: FC<ISelectProps> = ({
className, className,
@ -40,6 +41,7 @@ const Select: FC<ISelectProps> = ({
onSelect, onSelect,
allowSearch = true, allowSearch = true,
bgClassName = 'bg-gray-100', bgClassName = 'bg-gray-100',
overlayClassName,
}) => { }) => {
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@ -48,9 +50,9 @@ const Select: FC<ISelectProps> = ({
useEffect(() => { useEffect(() => {
let defaultSelect = null let defaultSelect = null
const existed = items.find((item: Item) => item.value === defaultValue) const existed = items.find((item: Item) => item.value === defaultValue)
if (existed) { if (existed)
defaultSelect = existed defaultSelect = existed
}
setSelectedItem(defaultSelect) setSelectedItem(defaultSelect)
}, [defaultValue]) }, [defaultValue])
@ -104,7 +106,7 @@ const Select: FC<ISelectProps> = ({
</div> </div>
{filteredItems.length > 0 && ( {filteredItems.length > 0 && (
<Combobox.Options className="absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm"> <Combobox.Options className={`absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm ${overlayClassName}`}>
{filteredItems.map((item: Item) => ( {filteredItems.map((item: Item) => (
<Combobox.Option <Combobox.Option
key={item.value} key={item.value}
@ -155,9 +157,9 @@ const SimpleSelect: FC<ISelectProps> = ({
useEffect(() => { useEffect(() => {
let defaultSelect = null let defaultSelect = null
const existed = items.find((item: Item) => item.value === defaultValue) const existed = items.find((item: Item) => item.value === defaultValue)
if (existed) { if (existed)
defaultSelect = existed defaultSelect = existed
}
setSelectedItem(defaultSelect) setSelectedItem(defaultSelect)
}, [defaultValue]) }, [defaultValue])
@ -173,7 +175,7 @@ const SimpleSelect: FC<ISelectProps> = ({
> >
<div className={`relative h-9 ${wrapperClassName}`}> <div className={`relative h-9 ${wrapperClassName}`}>
<Listbox.Button className={`w-full h-full rounded-lg border-0 bg-gray-100 py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200 cursor-pointer ${className}`}> <Listbox.Button className={`w-full h-full rounded-lg border-0 bg-gray-100 py-1.5 pl-3 pr-10 shadow-sm sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover:bg-gray-200 cursor-pointer ${className}`}>
<span className={classNames("block truncate text-left", !selectedItem?.name && 'text-gray-400')}>{selectedItem?.name ?? localPlaceholder}</span> <span className={classNames('block truncate text-left', !selectedItem?.name && 'text-gray-400')}>{selectedItem?.name ?? localPlaceholder}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronDownIcon <ChevronDownIcon
className="h-5 w-5 text-gray-400" className="h-5 w-5 text-gray-400"

View File

@ -395,21 +395,21 @@ const Main: FC<IMainProps> = ({
if (!inputs || !prompt_variables || prompt_variables?.length === 0) if (!inputs || !prompt_variables || prompt_variables?.length === 0)
return true return true
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs?.[key]) if (!inputs?.[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput

View File

@ -1,16 +1,16 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useState, useEffect } from 'react' import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import TemplateVarPanel, { PanelTitle, VarOpBtnGroup } from '../value-panel'
import s from './style.module.css' import s from './style.module.css'
import { AppInfo, ChatBtn, EditBtn, FootLogo, PromptTemplate } from './massive-component'
import type { SiteInfo } from '@/models/share' import type { SiteInfo } from '@/models/share'
import type { PromptConfig } from '@/models/debug' import type { PromptConfig } from '@/models/debug'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
import Select from '@/app/components/base/select' import Select from '@/app/components/base/select'
import { DEFAULT_VALUE_MAX_LEN } from '@/config' import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import TemplateVarPanel, { PanelTitle, VarOpBtnGroup } from '../value-panel'
import { AppInfo, PromptTemplate, ChatBtn, EditBtn, FootLogo } from './massive-component'
// regex to match the {{}} and replace it with a span // regex to match the {{}} and replace it with a span
const regex = /\{\{([^}]+)\}\}/g const regex = /\{\{([^}]+)\}\}/g
@ -38,15 +38,15 @@ const Welcome: FC<IWelcomeProps> = ({
onStartChat, onStartChat,
canEidtInpus, canEidtInpus,
savedInputs, savedInputs,
onInputsChange onInputsChange,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const hasVar = promptConfig.prompt_variables.length > 0 const hasVar = promptConfig.prompt_variables.length > 0
const [isFold, setIsFold] = useState<boolean>(true) const [isFold, setIsFold] = useState<boolean>(true)
const [inputs, setInputs] = useState<Record<string, any>>((() => { const [inputs, setInputs] = useState<Record<string, any>>((() => {
if (hasSetInputs) { if (hasSetInputs)
return savedInputs return savedInputs
}
const res: Record<string, any> = {} const res: Record<string, any> = {}
if (promptConfig) { if (promptConfig) {
promptConfig.prompt_variables.forEach((item) => { promptConfig.prompt_variables.forEach((item) => {
@ -65,7 +65,8 @@ const Welcome: FC<IWelcomeProps> = ({
}) })
} }
setInputs(res) setInputs(res)
} else { }
else {
setInputs(savedInputs) setInputs(savedInputs)
} }
}, [savedInputs]) }, [savedInputs])
@ -98,24 +99,26 @@ const Welcome: FC<IWelcomeProps> = ({
{promptConfig.prompt_variables.map(item => ( {promptConfig.prompt_variables.map(item => (
<div className='tablet:flex tablet:!h-9 mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}> <div className='tablet:flex tablet:!h-9 mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}>
<label className={`flex-shrink-0 flex items-center mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label> <label className={`flex-shrink-0 flex items-center mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label>
{item.type === 'select' ? ( {item.type === 'select'
<Select ? (
className='w-full' <Select
defaultValue={inputs?.[item.key]} className='w-full'
onSelect={(i) => { setInputs({ ...inputs, [item.key]: i.value }) }} defaultValue={inputs?.[item.key]}
items={(item.options || []).map(i => ({ name: i, value: i }))} onSelect={(i) => { setInputs({ ...inputs, [item.key]: i.value }) }}
allowSearch={false} items={(item.options || []).map(i => ({ name: i, value: i }))}
bgClassName='bg-gray-50' allowSearch={false}
/> bgClassName='bg-gray-50'
) : ( />
<input )
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`} : (
value={inputs?.[item.key] || ''} <input
onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }} placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
className={`w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50`} value={inputs?.[item.key] || ''}
maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN} onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }}
/> className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'}
)} maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN}
/>
)}
</div> </div>
))} ))}
</div> </div>
@ -124,34 +127,33 @@ const Welcome: FC<IWelcomeProps> = ({
const canChat = () => { const canChat = () => {
const prompt_variables = promptConfig?.prompt_variables const prompt_variables = promptConfig?.prompt_variables
if (!inputs || !prompt_variables || prompt_variables?.length === 0) { if (!inputs || !prompt_variables || prompt_variables?.length === 0)
return true return true
}
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) { if (hasEmptyInput)
return return
}
if (!inputs?.[key]) { if (!inputs?.[key])
hasEmptyInput = true hasEmptyInput = name
}
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput
} }
const handleChat = () => { const handleChat = () => {
if (!canChat()) { if (!canChat())
return return
}
onStartChat(inputs) onStartChat(inputs)
} }
@ -211,9 +213,9 @@ const Welcome: FC<IWelcomeProps> = ({
return ( return (
<VarOpBtnGroup <VarOpBtnGroup
onConfirm={() => { onConfirm={() => {
if (!canChat()) { if (!canChat())
return return
}
onInputsChange(inputs) onInputsChange(inputs)
setIsFold(true) setIsFold(true)
}} }}
@ -269,9 +271,9 @@ const Welcome: FC<IWelcomeProps> = ({
} }
const renderHasSetInputsPrivate = () => { const renderHasSetInputsPrivate = () => {
if (!canEidtInpus || !hasVar) { if (!canEidtInpus || !hasVar)
return null return null
}
return ( return (
<TemplateVarPanel <TemplateVarPanel
isFold={isFold} isFold={isFold}
@ -293,9 +295,9 @@ const Welcome: FC<IWelcomeProps> = ({
} }
const renderHasSetInputs = () => { const renderHasSetInputs = () => {
if (!isPublicVersion && !canEidtInpus || !hasVar) { if ((!isPublicVersion && !canEidtInpus) || !hasVar)
return null return null
}
return ( return (
<div <div
className='pt-[88px] mb-5' className='pt-[88px] mb-5'
@ -312,11 +314,13 @@ const Welcome: FC<IWelcomeProps> = ({
{ {
!hasSetInputs && ( !hasSetInputs && (
<div className='mobile:pt-[72px] tablet:pt-[128px] pc:pt-[200px]'> <div className='mobile:pt-[72px] tablet:pt-[128px] pc:pt-[200px]'>
{hasVar ? ( {hasVar
renderVarPanel() ? (
) : ( renderVarPanel()
renderNoVarPanel() )
)} : (
renderNoVarPanel()
)}
</div> </div>
) )
} }

View File

@ -390,21 +390,21 @@ const Main: FC<IMainProps> = ({
if (!inputs || !prompt_variables || prompt_variables?.length === 0) if (!inputs || !prompt_variables || prompt_variables?.length === 0)
return true return true
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs?.[key]) if (!inputs?.[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput

View File

@ -130,21 +130,21 @@ const Welcome: FC<IWelcomeProps> = ({
if (!inputs || !prompt_variables || prompt_variables?.length === 0) if (!inputs || !prompt_variables || prompt_variables?.length === 0)
return true return true
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs?.[key]) if (!inputs?.[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput

View File

@ -80,21 +80,21 @@ const Result: FC<IResultProps> = ({
if (!prompt_variables || prompt_variables?.length === 0) if (!prompt_variables || prompt_variables?.length === 0)
return true return true
let hasEmptyInput = false let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res return res
}) || [] // compatible with old version }) || [] // compatible with old version
requiredVars.forEach(({ key }) => { requiredVars.forEach(({ key, name }) => {
if (hasEmptyInput) if (hasEmptyInput)
return return
if (!inputs[key]) if (!inputs[key])
hasEmptyInput = true hasEmptyInput = name
}) })
if (hasEmptyInput) { if (hasEmptyInput) {
logError(t('appDebug.errorMessage.valueOfVarRequired')) logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput }))
return false return false
} }
return !hasEmptyInput return !hasEmptyInput

View File

@ -87,7 +87,7 @@ const translation = {
}, },
errorMessage: { errorMessage: {
nameOfKeyRequired: 'name of the key: {{key}} required', nameOfKeyRequired: 'name of the key: {{key}} required',
valueOfVarRequired: 'Variables value can not be empty', valueOfVarRequired: '{{key}} value can not be empty',
queryRequired: 'Request text is required.', queryRequired: 'Request text is required.',
waitForResponse: waitForResponse:
'Please wait for the response to the previous message to complete.', 'Please wait for the response to the previous message to complete.',

View File

@ -86,7 +86,7 @@ const translation = {
}, },
errorMessage: { errorMessage: {
nameOfKeyRequired: '变量 {{key}} 对应的名称必填', nameOfKeyRequired: '变量 {{key}} 对应的名称必填',
valueOfVarRequired: '变量值必填', valueOfVarRequired: '{{key}}必填',
queryRequired: '主要文本必填', queryRequired: '主要文本必填',
waitForResponse: '请等待上条信息响应完成', waitForResponse: '请等待上条信息响应完成',
waitForBatchResponse: '请等待批量任务完成', waitForBatchResponse: '请等待批量任务完成',