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> = ({ ...@@ -98,7 +98,7 @@ const DebugItem: FC<DebugItemProps> = ({
? [ ? [
{ {
value: 'remove', value: 'remove',
text: t('common.operation.remove'), text: t('common.operation.remove') as string,
}, },
] ]
: undefined : undefined
......
...@@ -8,23 +8,30 @@ import { ...@@ -8,23 +8,30 @@ import {
} from '@/app/components/base/portal-to-follow-elem' } from '@/app/components/base/portal-to-follow-elem'
export type Item = { export type Item = {
value: string value: string | number
text: string text: string | JSX.Element
} }
type DropdownProps = { type DropdownProps = {
items: Item[] items: Item[]
secondItems?: Item[] secondItems?: Item[]
onSelect: (item: Item) => void onSelect: (item: Item) => void
renderTrigger?: (open: boolean) => React.ReactNode renderTrigger?: (open: boolean) => React.ReactNode
popupClassName?: string
} }
const Dropdown: FC<DropdownProps> = ({ const Dropdown: FC<DropdownProps> = ({
items, items,
onSelect, onSelect,
secondItems, secondItems,
renderTrigger, renderTrigger,
popupClassName,
}) => { }) => {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const handleSelect = (item: Item) => {
setOpen(false)
onSelect(item)
}
return ( return (
<PortalToFollowElem <PortalToFollowElem
open={open} open={open}
...@@ -47,7 +54,7 @@ const Dropdown: FC<DropdownProps> = ({ ...@@ -47,7 +54,7 @@ const Dropdown: FC<DropdownProps> = ({
) )
} }
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent> <PortalToFollowElemContent className={popupClassName}>
<div className='rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg text-sm text-gray-700'> <div className='rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg text-sm text-gray-700'>
{ {
!!items.length && ( !!items.length && (
...@@ -57,7 +64,7 @@ const Dropdown: FC<DropdownProps> = ({ ...@@ -57,7 +64,7 @@ const Dropdown: FC<DropdownProps> = ({
<div <div
key={item.value} key={item.value}
className='flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100' className='flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100'
onClick={() => onSelect(item)} onClick={() => handleSelect(item)}
> >
{item.text} {item.text}
</div> </div>
...@@ -79,7 +86,7 @@ const Dropdown: FC<DropdownProps> = ({ ...@@ -79,7 +86,7 @@ const Dropdown: FC<DropdownProps> = ({
<div <div
key={item.value} key={item.value}
className='flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100' className='flex items-center px-3 h-8 rounded-lg cursor-pointer hover:bg-gray-100'
onClick={() => onSelect(item)} onClick={() => handleSelect(item)}
> >
{item.text} {item.text}
</div> </div>
......
...@@ -4,7 +4,6 @@ import type { ...@@ -4,7 +4,6 @@ import type {
} from 'react' } from 'react'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import cn from 'classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import type { import type {
DefaultModel, DefaultModel,
...@@ -21,6 +20,7 @@ import ParameterItem from './parameter-item' ...@@ -21,6 +20,7 @@ import ParameterItem from './parameter-item'
import type { ParameterValue } from './parameter-item' import type { ParameterValue } from './parameter-item'
import Trigger from './trigger' import Trigger from './trigger'
import type { TriggerProps } from './trigger' import type { TriggerProps } from './trigger'
import PresetsParameter from './presets-parameter'
import { import {
PortalToFollowElem, PortalToFollowElem,
PortalToFollowElemContent, PortalToFollowElemContent,
...@@ -30,13 +30,7 @@ import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes' ...@@ -30,13 +30,7 @@ import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
import { fetchModelParameterRules } from '@/service/common' import { fetchModelParameterRules } from '@/service/common'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import Radio from '@/app/components/base/radio'
import { TONE_LIST } from '@/config' 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' import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
export type ModelParameterModalProps = { export type ModelParameterModalProps = {
...@@ -84,8 +78,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({ ...@@ -84,8 +78,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { hasSettedApiKey } = useProviderContext() const { hasSettedApiKey } = useProviderContext()
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
const [open, setOpen] = useState(false) 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 { data: parameterRulesData, isLoading } = useSWR((provider && modelId) ? `/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}` : null, fetchModelParameterRules)
const { const {
...@@ -100,46 +92,10 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({ ...@@ -100,46 +92,10 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
const modelDisabled = currentModel?.status !== ModelStatusEnum.active const modelDisabled = currentModel?.status !== ModelStatusEnum.active
const disabled = !hasSettedApiKey || hasDeprecated || modelDisabled const disabled = !hasSettedApiKey || hasDeprecated || modelDisabled
const parameterRules = useMemo(() => { const parameterRules: ModelParameterRule[] = useMemo(() => {
return parameterRulesData?.data || [] return parameterRulesData?.data || []
}, [parameterRulesData]) }, [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) => { const handleParamChange = (key: string, value: ParameterValue) => {
onCompletionParamsChange({ onCompletionParamsChange({
...completionParams, ...completionParams,
...@@ -175,7 +131,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({ ...@@ -175,7 +131,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
const handleInitialParams = () => { const handleInitialParams = () => {
const newCompletionParams = { ...completionParams } const newCompletionParams = { ...completionParams }
const defaultParams: Record<string, any> = {}
if (parameterRules.length) { if (parameterRules.length) {
parameterRules.forEach((parameterRule) => { parameterRules.forEach((parameterRule) => {
if (!newCompletionParams[parameterRule.name]) { if (!newCompletionParams[parameterRule.name]) {
...@@ -184,13 +139,8 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({ ...@@ -184,13 +139,8 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
else else
delete newCompletionParams[parameterRule.name] 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) onCompletionParamsChange(newCompletionParams)
} }
} }
...@@ -199,15 +149,14 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({ ...@@ -199,15 +149,14 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
handleInitialParams() handleInitialParams()
}, [parameterRules]) }, [parameterRules])
const getToneIcon = (toneId: number) => { const handleSelectPresetParameter = (toneId: number) => {
const className = 'w-[14px] h-[14px]' const tone = TONE_LIST.find(tone => tone.id === toneId)
const res = ({ if (tone) {
1: <Brush01 className={className} />, onCompletionParamsChange({
2: <Scales02 className={className} />, ...completionParams,
3: <Target04 className={className} />, ...tone.config,
4: <Sliders02 className={className} />, })
})[toneId] }
return res
} }
return ( return (
...@@ -274,47 +223,18 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({ ...@@ -274,47 +223,18 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
<div className='mt-5'><Loading /></div> <div className='mt-5'><Loading /></div>
) )
} }
{PROVIDER_WITH_PRESET_TONE.includes(provider) && !isLoading && !!parameterRules.length && ( {
<div className='mt-5 mb-4'> !isLoading && !!parameterRules.length && (
<div className="mb-3 text-sm text-gray-900">{t('appDebug.modelConfig.setTone')}</div> <div className='flex items-center justify-between mb-4'>
<Radio.Group className={cn('!rounded-lg', toneTabBgClassName)} value={toneId} onChange={handleToneChange}> <div className='text-gray-900 font-semibold'>{t('common.modelProvider.parameters')}</div>
<> {
{TONE_LIST.slice(0, 3).map(tone => ( PROVIDER_WITH_PRESET_TONE.includes(provider) && (
<div className='grow flex items-center' key={tone.id}> <PresetsParameter onSelect={handleSelectPresetParameter} />
<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')} </div>
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>
</div>
)}
{ {
!isLoading && !!parameterRules.length && ( !isLoading && !!parameterRules.length && (
[ [
......
import type { FC } from 'react' import type { FC } from 'react'
import { useState } from 'react' import { useEffect, useRef, useState } from 'react'
import type { ModelParameterRule } from '../declarations' import type { ModelParameterRule } from '../declarations'
import { useLanguage } from '../hooks' import { useLanguage } from '../hooks'
import { isNullOrUndefined } from '../utils' import { isNullOrUndefined } from '../utils'
...@@ -29,6 +29,7 @@ const ParameterItem: FC<ParameterItemProps> = ({ ...@@ -29,6 +29,7 @@ const ParameterItem: FC<ParameterItemProps> = ({
}) => { }) => {
const language = useLanguage() const language = useLanguage()
const [localValue, setLocalValue] = useState(value) const [localValue, setLocalValue] = useState(value)
const numberInputRef = useRef<HTMLInputElement>(null)
const getDefaultValue = () => { const getDefaultValue = () => {
let defaultValue: ParameterValue let defaultValue: ParameterValue
...@@ -57,8 +58,10 @@ const ParameterItem: FC<ParameterItemProps> = ({ ...@@ -57,8 +58,10 @@ const ParameterItem: FC<ParameterItemProps> = ({
const handleNumberInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleNumberInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let num = +e.target.value let num = +e.target.value
if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) {
num = parameterRule.max as number num = parameterRule.max as number
numberInputRef.current!.value = `${num}`
}
if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!) if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!)
num = parameterRule.min as number num = parameterRule.min as number
...@@ -66,14 +69,26 @@ const ParameterItem: FC<ParameterItemProps> = ({ ...@@ -66,14 +69,26 @@ const ParameterItem: FC<ParameterItemProps> = ({
handleInputChange(num) handleInputChange(num)
} }
const handleNumberInputBlur = () => {
if (numberInputRef.current)
numberInputRef.current.value = renderValue as string
}
const handleSlideChange = (num: number) => { const handleSlideChange = (num: number) => {
if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) if (!isNullOrUndefined(parameterRule.max) && num > parameterRule.max!) {
return handleInputChange(parameterRule.max) handleInputChange(parameterRule.max)
numberInputRef.current!.value = `${parameterRule.max}`
return
}
if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!) if (!isNullOrUndefined(parameterRule.min) && num < parameterRule.min!) {
return handleInputChange(parameterRule.min) handleInputChange(parameterRule.min)
numberInputRef.current!.value = `${parameterRule.min}`
return
}
handleInputChange(num) handleInputChange(num)
numberInputRef.current!.value = `${num}`
} }
const handleRadioChange = (v: number) => { const handleRadioChange = (v: number) => {
...@@ -129,13 +144,14 @@ const ParameterItem: FC<ParameterItemProps> = ({ ...@@ -129,13 +144,14 @@ const ParameterItem: FC<ParameterItemProps> = ({
onChange={handleSlideChange} onChange={handleSlideChange}
/>} />}
<input <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' 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' type='number'
max={parameterRule.max} max={parameterRule.max}
min={parameterRule.min} min={parameterRule.min}
step={numberInputWithSlide ? step : +`0.${parameterRule.precision || 0}`} step={numberInputWithSlide ? step : +`0.${parameterRule.precision || 0}`}
value={renderValue as string}
onChange={handleNumberInputChange} onChange={handleNumberInputChange}
onBlur={handleNumberInputBlur}
/> />
</> </>
) )
...@@ -191,6 +207,11 @@ const ParameterItem: FC<ParameterItemProps> = ({ ...@@ -191,6 +207,11 @@ const ParameterItem: FC<ParameterItemProps> = ({
return null return null
} }
useEffect(() => {
if (numberInputRef.current)
numberInputRef.current.value = `${renderValue}`
}, [])
return ( return (
<div className={`flex items-center justify-between ${className}`}> <div className={`flex items-center justify-between ${className}`}>
<div> <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 = { ...@@ -309,6 +309,8 @@ const translation = {
deprecated: 'Deprecated', deprecated: 'Deprecated',
confirmDelete: 'confirm deletion?', confirmDelete: 'confirm deletion?',
quotaTip: 'Remaining available free tokens', quotaTip: 'Remaining available free tokens',
loadPresets: 'Load Presents',
parameters: 'PARAMETERS',
}, },
dataSource: { dataSource: {
add: 'Add a data source', add: 'Add a data source',
......
...@@ -138,6 +138,8 @@ const translation = { ...@@ -138,6 +138,8 @@ const translation = {
deprecated: 'Descontinuado', deprecated: 'Descontinuado',
confirmDelete: 'confirmar exclusão?', confirmDelete: 'confirmar exclusão?',
quotaTip: 'Tokens gratuitos disponíveis restantes', quotaTip: 'Tokens gratuitos disponíveis restantes',
loadPresets: 'Carregar presentes',
parameters: 'PARÂMETROS',
}, },
dataSource: { dataSource: {
add: 'Adicionar uma fonte de dados', add: 'Adicionar uma fonte de dados',
......
...@@ -309,6 +309,8 @@ const translation = { ...@@ -309,6 +309,8 @@ const translation = {
deprecated: '已弃用', deprecated: '已弃用',
confirmDelete: '确认删除?', confirmDelete: '确认删除?',
quotaTip: '剩余免费额度', quotaTip: '剩余免费额度',
loadPresets: '加载预设',
parameters: '参数',
}, },
dataSource: { dataSource: {
add: '添加数据源', 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