| import PluginDependency from '@/app/components/workflow/plugin-dependency' | import PluginDependency from '@/app/components/workflow/plugin-dependency' | ||||
| import { supportFunctionCall } from '@/utils/tool-call' | import { supportFunctionCall } from '@/utils/tool-call' | ||||
| import { MittProvider } from '@/context/mitt-context' | import { MittProvider } from '@/context/mitt-context' | ||||
| import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' | |||||
| import Toast from '@/app/components/base/toast' | |||||
| type PublishConfig = { | type PublishConfig = { | ||||
| modelConfig: ModelConfig | modelConfig: ModelConfig | ||||
| ...visionConfig, | ...visionConfig, | ||||
| enabled: supportVision, | enabled: supportVision, | ||||
| }, true) | }, 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) | const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision) |
| import StructureOutput from './components/structure-output' | import StructureOutput from './components/structure-output' | ||||
| import Switch from '@/app/components/base/switch' | import Switch from '@/app/components/base/switch' | ||||
| import { RiAlertFill, RiQuestionLine } from '@remixicon/react' | import { RiAlertFill, RiQuestionLine } from '@remixicon/react' | ||||
| import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params' | |||||
| import Toast from '@/app/components/base/toast' | |||||
| const i18nPrefix = 'workflow.nodes.llm' | const i18nPrefix = 'workflow.nodes.llm' | ||||
| modelId: string | modelId: string | ||||
| mode?: 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 ( | return ( | ||||
| <div className='mt-2'> | <div className='mt-2'> |
| 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 ?? []) | |||||
| } |