Unverified Commit 4ff17af5 authored by zxhlyh's avatar zxhlyh Committed by GitHub

fix: model parameter modal input (#2206)

parent a9d1b4e6
......@@ -98,7 +98,7 @@ const DebugItem: FC<DebugItemProps> = ({
? [
{
value: 'remove',
text: t('common.operation.remove'),
text: t('common.operation.remove') as string,
},
]
: undefined
......
......@@ -8,23 +8,30 @@ import {
} from '@/app/components/base/portal-to-follow-elem'
export type Item = {
value: string
text: string
value: string | number
text: string | JSX.Element
}
type DropdownProps = {
items: Item[]
secondItems?: Item[]
onSelect: (item: Item) => void
renderTrigger?: (open: boolean) => React.ReactNode
popupClassName?: string
}
const Dropdown: FC<DropdownProps> = ({
items,
onSelect,
secondItems,
renderTrigger,
popupClassName,
}) => {
const [open, setOpen] = useState(false)
const handleSelect = (item: Item) => {
setOpen(false)
onSelect(item)
}
return (
<PortalToFollowElem
open={open}
......@@ -47,7 +54,7 @@ const Dropdown: FC<DropdownProps> = ({
)
}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<PortalToFollowElemContent className={popupClassName}>
<div className='rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg text-sm text-gray-700'>
{
!!items.length && (
......@@ -57,7 +64,7 @@ const Dropdown: FC<DropdownProps> = ({
<div
key={item.value}
className='flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100'
onClick={() => onSelect(item)}
onClick={() => handleSelect(item)}
>
{item.text}
</div>
......@@ -79,7 +86,7 @@ const Dropdown: FC<DropdownProps> = ({
<div
key={item.value}
className='flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100'
onClick={() => onSelect(item)}
onClick={() => handleSelect(item)}
>
{item.text}
</div>
......
......@@ -4,7 +4,6 @@ import type {
} from 'react'
import { useEffect, useMemo, useState } from 'react'
import useSWR from 'swr'
import cn from 'classnames'
import { useTranslation } from 'react-i18next'
import type {
DefaultModel,
......@@ -21,6 +20,7 @@ import ParameterItem from './parameter-item'
import type { ParameterValue } from './parameter-item'
import Trigger from './trigger'
import type { TriggerProps } from './trigger'
import PresetsParameter from './presets-parameter'
import {
PortalToFollowElem,
PortalToFollowElemContent,
......@@ -30,13 +30,7 @@ import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
import { fetchModelParameterRules } from '@/service/common'
import Loading from '@/app/components/base/loading'
import { useProviderContext } from '@/context/provider-context'
import Radio from '@/app/components/base/radio'
import { TONE_LIST } from '@/config'
import { Brush01 } from '@/app/components/base/icons/src/vender/solid/editor'
import { Scales02 } from '@/app/components/base/icons/src/vender/solid/FinanceAndECommerce'
import { Target04 } from '@/app/components/base/icons/src/vender/solid/general'
import { Sliders02 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
export type ModelParameterModalProps = {
......@@ -84,8 +78,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
}) => {
const { t } = useTranslation()
const { hasSettedApiKey } = useProviderContext()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const [open, setOpen] = useState(false)
const { data: parameterRulesData, isLoading } = useSWR((provider && modelId) ? `/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}` : null, fetchModelParameterRules)
const {
......@@ -100,46 +92,10 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
const modelDisabled = currentModel?.status !== ModelStatusEnum.active
const disabled = !hasSettedApiKey || hasDeprecated || modelDisabled
const parameterRules = useMemo(() => {
const parameterRules: ModelParameterRule[] = useMemo(() => {
return parameterRulesData?.data || []
}, [parameterRulesData])
// only openai support this
function matchToneId(completionParams: FormValue): number {
const remvoedCustomeTone = TONE_LIST.slice(0, -1)
const CUSTOM_TONE_ID = 4
const tone = remvoedCustomeTone.find((tone) => {
const config: Record<string, any> = tone.config || {}
return Object.keys(config).every((key) => {
return config[key] === completionParams[key]
})
})
return tone ? tone.id : CUSTOM_TONE_ID
}
// tone is a preset of completionParams.
const [toneId, setToneId] = useState(matchToneId(completionParams)) // default is Balanced
const toneTabBgClassName = ({
1: 'bg-[#F5F8FF]',
2: 'bg-[#F4F3FF]',
3: 'bg-[#F6FEFC]',
})[toneId] || ''
// set completionParams by toneId
const handleToneChange = (id: number) => {
const tone = TONE_LIST.find(tone => tone.id === id)
if (tone) {
setToneId(id)
onCompletionParamsChange({
...tone.config,
})
}
}
useEffect(() => {
setToneId(matchToneId(completionParams))
}, [completionParams])
const handleParamChange = (key: string, value: ParameterValue) => {
onCompletionParamsChange({
...completionParams,
......@@ -175,7 +131,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
const handleInitialParams = () => {
const newCompletionParams = { ...completionParams }
const defaultParams: Record<string, any> = {}
if (parameterRules.length) {
parameterRules.forEach((parameterRule) => {
if (!newCompletionParams[parameterRule.name]) {
......@@ -184,13 +139,8 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
else
delete newCompletionParams[parameterRule.name]
}
if (!isNullOrUndefined(parameterRule.default))
defaultParams[parameterRule.name] = parameterRule.default
})
if (PROVIDER_WITH_PRESET_TONE.includes(provider))
TONE_LIST[3].config = defaultParams as any
onCompletionParamsChange(newCompletionParams)
}
}
......@@ -199,15 +149,14 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
handleInitialParams()
}, [parameterRules])
const getToneIcon = (toneId: number) => {
const className = 'w-[14px] h-[14px]'
const res = ({
1: <Brush01 className={className} />,
2: <Scales02 className={className} />,
3: <Target04 className={className} />,
4: <Sliders02 className={className} />,
})[toneId]
return res
const handleSelectPresetParameter = (toneId: number) => {
const tone = TONE_LIST.find(tone => tone.id === toneId)
if (tone) {
onCompletionParamsChange({
...completionParams,
...tone.config,
})
}
}
return (
......@@ -274,47 +223,18 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
<div className='mt-5'><Loading /></div>
)
}
{PROVIDER_WITH_PRESET_TONE.includes(provider) && !isLoading && !!parameterRules.length && (
<div className='mt-5 mb-4'>
<div className="mb-3 text-sm text-gray-900">{t('appDebug.modelConfig.setTone')}</div>
<Radio.Group className={cn('!rounded-lg', toneTabBgClassName)} value={toneId} onChange={handleToneChange}>
<>
{TONE_LIST.slice(0, 3).map(tone => (
<div className='grow flex items-center' key={tone.id}>
<Radio
value={tone.id}
className={cn(tone.id === toneId && 'rounded-md border border-gray-200 shadow-md', '!mr-0 grow !px-1 sm:!px-2 !justify-center text-[13px] font-medium')}
labelClassName={cn(tone.id === toneId
? ({
1: 'text-[#6938EF]',
2: 'text-[#444CE7]',
3: 'text-[#107569]',
})[toneId]
: 'text-[#667085]', 'flex items-center space-x-2')}
>
<>
{getToneIcon(tone.id)}
{!isMobile && <div>{t(`common.model.tone.${tone.name}`) as string}</div>}
<div className=""></div>
</>
</Radio>
{tone.id !== toneId && tone.id + 1 !== toneId && (<div className='h-5 border-r border-gray-200'></div>)}
</div>
))}
</>
<Radio
value={TONE_LIST[3].id}
className={cn(toneId === 4 && 'rounded-md border border-gray-200 shadow-md', '!mr-0 grow !px-1 sm:!px-2 !justify-center text-[13px] font-medium')}
labelClassName={cn('flex items-center space-x-2 ', toneId === 4 ? 'text-[#155EEF]' : 'text-[#667085]')}
>
<>
{getToneIcon(TONE_LIST[3].id)}
{!isMobile && <div>{t(`common.model.tone.${TONE_LIST[3].name}`) as string}</div>}
</>
</Radio>
</Radio.Group>
{
!isLoading && !!parameterRules.length && (
<div className='flex items-center justify-between mb-4'>
<div className='text-gray-900 font-semibold'>{t('common.modelProvider.parameters')}</div>
{
PROVIDER_WITH_PRESET_TONE.includes(provider) && (
<PresetsParameter onSelect={handleSelectPresetParameter} />
)
}
</div>
)}
)
}
{
!isLoading && !!parameterRules.length && (
[
......
import type { FC } from 'react'
import { useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import type { ModelParameterRule } from '../declarations'
import { useLanguage } from '../hooks'
import { isNullOrUndefined } from '../utils'
......@@ -29,6 +29,7 @@ const ParameterItem: FC<ParameterItemProps> = ({
}) => {
const language = useLanguage()
const [localValue, setLocalValue] = useState(value)
const numberInputRef = useRef<HTMLInputElement>(null)
const getDefaultValue = () => {
let defaultValue: ParameterValue
......@@ -57,8 +58,10 @@ const ParameterItem: FC<ParameterItemProps> = ({
const handleNumberInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let num = +e.target.value
if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!)
if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) {
num = parameterRule.max as number
numberInputRef.current!.value = `${num}`
}
if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!)
num = parameterRule.min as number
......@@ -66,14 +69,26 @@ const ParameterItem: FC<ParameterItemProps> = ({
handleInputChange(num)
}
const handleNumberInputBlur = () => {
if (numberInputRef.current)
numberInputRef.current.value = renderValue as string
}
const handleSlideChange = (num: number) => {
if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!)
return handleInputChange(parameterRule.max)
if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) {
handleInputChange(parameterRule.max)
numberInputRef.current!.value = `${parameterRule.max}`
return
}
if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!)
return handleInputChange(parameterRule.min)
if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!) {
handleInputChange(parameterRule.min)
numberInputRef.current!.value = `${parameterRule.min}`
return
}
handleInputChange(num)
numberInputRef.current!.value = `${num}`
}
const handleRadioChange = (v: number) => {
......@@ -129,13 +144,14 @@ const ParameterItem: FC<ParameterItemProps> = ({
onChange={handleSlideChange}
/>}
<input
ref={numberInputRef}
className='shrink-0 block ml-4 pl-3 w-16 h-8 appearance-none outline-none rounded-lg bg-gray-100 text-[13px] text-gra-900'
type='number'
max={parameterRule.max}
min={parameterRule.min}
step={numberInputWithSlide ? step : +`0.${parameterRule.precision || 0}`}
value={renderValue as string}
onChange={handleNumberInputChange}
onBlur={handleNumberInputBlur}
/>
</>
)
......@@ -191,6 +207,11 @@ const ParameterItem: FC<ParameterItemProps> = ({
return null
}
useEffect(() => {
if (numberInputRef.current)
numberInputRef.current.value = `${renderValue}`
}, [])
return (
<div className={`flex items-center justify-between ${className}`}>
<div>
......
import type { FC } from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Dropdown from '@/app/components/base/dropdown'
import { SlidersH } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import { Brush01 } from '@/app/components/base/icons/src/vender/solid/editor'
import { Scales02 } from '@/app/components/base/icons/src/vender/solid/FinanceAndECommerce'
import { Target04 } from '@/app/components/base/icons/src/vender/solid/general'
import { TONE_LIST } from '@/config'
type PresetsParameterProps = {
onSelect: (toneId: number) => void
}
const PresetsParameter: FC<PresetsParameterProps> = ({
onSelect,
}) => {
const { t } = useTranslation()
const renderTrigger = useCallback((open: boolean) => {
return (
<div
className={`
flex items-center px-[7px] h-7 rounded-md border-[0.5px] border-gray-200 shadow-xs
text-xs font-medium text-gray-700 cursor-pointer
${open && 'bg-gray-100'}
`}
>
<SlidersH className='mr-[5px] w-3.5 h-3.5 text-gray-500' />
{t('common.modelProvider.loadPresets')}
<ChevronDown className='ml-0.5 w-3.5 h-3.5 text-gray-500' />
</div>
)
}, [])
const getToneIcon = (toneId: number) => {
const className = 'mr-2 w-[14px] h-[14px]'
const res = ({
1: <Brush01 className={`${className} text-[#6938EF]`} />,
2: <Scales02 className={`${className} text-indigo-600`} />,
3: <Target04 className={`${className} text-[#107569]`} />,
})[toneId]
return res
}
const options = TONE_LIST.slice(0, 3).map((tone) => {
return {
value: tone.id,
text: (
<div className='flex items-center h-full'>
{getToneIcon(tone.id)}
{t(`common.model.tone.${tone.name}`) as string}
</div>
),
}
})
return (
<Dropdown
renderTrigger={renderTrigger}
items={options}
onSelect={item => onSelect(item.value as number)}
popupClassName='z-[70]'
/>
)
}
export default PresetsParameter
......@@ -309,6 +309,8 @@ const translation = {
deprecated: 'Deprecated',
confirmDelete: 'confirm deletion?',
quotaTip: 'Remaining available free tokens',
loadPresets: 'Load Presents',
parameters: 'PARAMETERS',
},
dataSource: {
add: 'Add a data source',
......
......@@ -138,6 +138,8 @@ const translation = {
deprecated: 'Descontinuado',
confirmDelete: 'confirmar exclusão?',
quotaTip: 'Tokens gratuitos disponíveis restantes',
loadPresets: 'Carregar presentes',
parameters: 'PARÂMETROS',
},
dataSource: {
add: 'Adicionar uma fonte de dados',
......
......@@ -309,6 +309,8 @@ const translation = {
deprecated: '已弃用',
confirmDelete: '确认删除?',
quotaTip: '剩余免费额度',
loadPresets: '加载预设',
parameters: '参数',
},
dataSource: {
add: '添加数据源',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment