| @@ -80,6 +80,8 @@ import { | |||
| import PluginDependency from '@/app/components/workflow/plugin-dependency' | |||
| import { supportFunctionCall } from '@/utils/tool-call' | |||
| import { MittProvider } from '@/context/mitt-context' | |||
| import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' | |||
| import Toast from '@/app/components/base/toast' | |||
| type PublishConfig = { | |||
| modelConfig: ModelConfig | |||
| @@ -453,7 +455,21 @@ const Configuration: FC = () => { | |||
| ...visionConfig, | |||
| enabled: supportVision, | |||
| }, true) | |||
| setCompletionParams({}) | |||
| try { | |||
| const { params: filtered, removedDetails } = await fetchAndMergeValidCompletionParams( | |||
| provider, | |||
| modelId, | |||
| completionParams, | |||
| ) | |||
| if (Object.keys(removedDetails).length) | |||
| Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${Object.entries(removedDetails).map(([k, reason]) => `${k} (${reason})`).join(', ')}` }) | |||
| setCompletionParams(filtered) | |||
| } | |||
| catch (e) { | |||
| Toast.notify({ type: 'error', message: t('common.error') }) | |||
| setCompletionParams({}) | |||
| } | |||
| } | |||
| const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision) | |||
| @@ -19,6 +19,8 @@ import Editor from '@/app/components/workflow/nodes/_base/components/prompt/edit | |||
| import StructureOutput from './components/structure-output' | |||
| import Switch from '@/app/components/base/switch' | |||
| import { RiAlertFill, RiQuestionLine } from '@remixicon/react' | |||
| import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' | |||
| import Toast from '@/app/components/base/toast' | |||
| const i18nPrefix = 'workflow.nodes.llm' | |||
| @@ -68,10 +70,27 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({ | |||
| modelId: string | |||
| mode?: string | |||
| }) => { | |||
| handleCompletionParamsChange({}) | |||
| handleModelChanged(model) | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, []) | |||
| (async () => { | |||
| try { | |||
| const { params: filtered, removedDetails } = await fetchAndMergeValidCompletionParams( | |||
| model.provider, | |||
| model.modelId, | |||
| inputs.model.completion_params, | |||
| ) | |||
| const keys = Object.keys(removedDetails) | |||
| if (keys.length) | |||
| Toast.notify({ type: 'warning', message: `${t('common.modelProvider.parametersInvalidRemoved')}: ${keys.map(k => `${k} (${removedDetails[k]})`).join(', ')}` }) | |||
| handleCompletionParamsChange(filtered) | |||
| } | |||
| catch (e) { | |||
| Toast.notify({ type: 'error', message: t('common.error') }) | |||
| handleCompletionParamsChange({}) | |||
| } | |||
| finally { | |||
| handleModelChanged(model) | |||
| } | |||
| })() | |||
| }, [inputs.model.completion_params]) | |||
| return ( | |||
| <div className='mt-2'> | |||
| @@ -0,0 +1,88 @@ | |||
| import type { FormValue, ModelParameterRule } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| export const mergeValidCompletionParams = ( | |||
| oldParams: FormValue | undefined, | |||
| rules: ModelParameterRule[], | |||
| ): { params: FormValue; removedDetails: Record<string, string> } => { | |||
| if (!oldParams || Object.keys(oldParams).length === 0) | |||
| return { params: {}, removedDetails: {} } | |||
| const acceptedKeys = new Set(rules.map(r => r.name)) | |||
| const ruleMap: Record<string, ModelParameterRule> = {} | |||
| rules.forEach((r) => { | |||
| ruleMap[r.name] = r | |||
| }) | |||
| const nextParams: FormValue = {} | |||
| const removedDetails: Record<string, string> = {} | |||
| Object.entries(oldParams).forEach(([key, value]) => { | |||
| if (!acceptedKeys.has(key)) { | |||
| removedDetails[key] = 'unsupported' | |||
| return | |||
| } | |||
| const rule = ruleMap[key] | |||
| if (!rule) { | |||
| removedDetails[key] = 'unsupported' | |||
| return | |||
| } | |||
| switch (rule.type) { | |||
| case 'int': | |||
| case 'float': { | |||
| if (typeof value !== 'number') { | |||
| removedDetails[key] = 'invalid type' | |||
| return | |||
| } | |||
| const min = rule.min ?? Number.NEGATIVE_INFINITY | |||
| const max = rule.max ?? Number.POSITIVE_INFINITY | |||
| if (value < min || value > max) { | |||
| removedDetails[key] = `out of range (${min}-${max})` | |||
| return | |||
| } | |||
| nextParams[key] = value | |||
| return | |||
| } | |||
| case 'boolean': { | |||
| if (typeof value !== 'boolean') { | |||
| removedDetails[key] = 'invalid type' | |||
| return | |||
| } | |||
| nextParams[key] = value | |||
| return | |||
| } | |||
| case 'string': | |||
| case 'text': { | |||
| if (typeof value !== 'string') { | |||
| removedDetails[key] = 'invalid type' | |||
| return | |||
| } | |||
| if (Array.isArray(rule.options) && rule.options.length) { | |||
| if (!(rule.options as string[]).includes(value)) { | |||
| removedDetails[key] = 'unsupported option' | |||
| return | |||
| } | |||
| } | |||
| nextParams[key] = value | |||
| return | |||
| } | |||
| default: { | |||
| removedDetails[key] = `unsupported rule type: ${(rule as any)?.type ?? 'unknown'}` | |||
| } | |||
| } | |||
| }) | |||
| return { params: nextParams, removedDetails } | |||
| } | |||
| export const fetchAndMergeValidCompletionParams = async ( | |||
| provider: string, | |||
| modelId: string, | |||
| oldParams: FormValue | undefined, | |||
| ): Promise<{ params: FormValue; removedDetails: Record<string, string> }> => { | |||
| const { fetchModelParameterRules } = await import('@/service/common') | |||
| const url = `/workspaces/current/model-providers/${provider}/models/parameter-rules?model=${modelId}` | |||
| const { data: parameterRules } = await fetchModelParameterRules(url) | |||
| return mergeValidCompletionParams(oldParams, parameterRules ?? []) | |||
| } | |||