@lobehub/lobehub 2.0.0-next.262 → 2.0.0-next.264
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/apps/desktop/src/main/core/browser/Browser.ts +14 -0
- package/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +32 -0
- package/changelog/v1.json +18 -0
- package/locales/zh-CN/chat.json +1 -0
- package/locales/zh-CN/modelProvider.json +20 -0
- package/package.json +1 -1
- package/packages/database/src/models/aiModel.ts +2 -0
- package/packages/database/src/repositories/aiInfra/index.test.ts +41 -1
- package/packages/database/src/repositories/aiInfra/index.ts +3 -1
- package/packages/model-runtime/src/providers/openrouter/index.test.ts +9 -55
- package/packages/model-runtime/src/providers/openrouter/index.ts +47 -27
- package/packages/model-runtime/src/providers/openrouter/type.ts +16 -28
- package/packages/model-runtime/src/providers/vercelaigateway/index.test.ts +6 -6
- package/packages/model-runtime/src/providers/vercelaigateway/index.ts +54 -11
- package/packages/model-runtime/src/utils/modelParse.test.ts +185 -3
- package/packages/model-runtime/src/utils/modelParse.ts +108 -1
- package/packages/types/src/llm.ts +3 -1
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/ExtendParamsSelect.tsx +398 -0
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/Form.tsx +11 -2
- package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/__tests__/ExtendParamsSelect.test.tsx +59 -0
- package/src/features/ChatInput/ActionBar/Model/ControlsForm.tsx +1 -1
- package/src/features/ChatInput/ActionBar/Model/GPT51ReasoningEffortSlider.tsx +9 -54
- package/src/features/ChatInput/ActionBar/Model/GPT52ProReasoningEffortSlider.tsx +9 -53
- package/src/features/ChatInput/ActionBar/Model/GPT52ReasoningEffortSlider.tsx +9 -55
- package/src/features/ChatInput/ActionBar/Model/GPT5ReasoningEffortSlider.tsx +9 -54
- package/src/features/ChatInput/ActionBar/Model/ImageAspectRatioSelect.tsx +50 -16
- package/src/features/ChatInput/ActionBar/Model/ImageResolutionSlider.tsx +7 -53
- package/src/features/ChatInput/ActionBar/Model/LevelSlider.tsx +92 -0
- package/src/features/ChatInput/ActionBar/Model/ReasoningEffortSlider.tsx +9 -53
- package/src/features/ChatInput/ActionBar/Model/TextVerbositySlider.tsx +9 -53
- package/src/features/ChatInput/ActionBar/Model/ThinkingLevel2Slider.tsx +9 -52
- package/src/features/ChatInput/ActionBar/Model/ThinkingLevelSlider.tsx +9 -54
- package/src/features/ChatInput/ActionBar/Model/ThinkingSlider.tsx +20 -56
- package/src/features/ChatInput/ActionBar/Model/__tests__/createLevelSlider.test.tsx +126 -0
- package/src/features/ChatInput/ActionBar/Model/createLevelSlider.tsx +105 -0
- package/src/locales/default/chat.ts +1 -0
- package/src/locales/default/modelProvider.ts +38 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { Flexbox } from '@lobehub/ui';
|
|
2
|
+
import { Popover, Select, Space, Switch, Tag, Typography, theme } from 'antd';
|
|
3
|
+
import type { ExtendParamsType } from 'model-bank';
|
|
4
|
+
import { memo, useMemo } from 'react';
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
import { Trans, useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
import GPT5ReasoningEffortSlider from '@/features/ChatInput/ActionBar/Model/GPT5ReasoningEffortSlider';
|
|
9
|
+
import GPT51ReasoningEffortSlider from '@/features/ChatInput/ActionBar/Model/GPT51ReasoningEffortSlider';
|
|
10
|
+
import GPT52ProReasoningEffortSlider from '@/features/ChatInput/ActionBar/Model/GPT52ProReasoningEffortSlider';
|
|
11
|
+
import GPT52ReasoningEffortSlider from '@/features/ChatInput/ActionBar/Model/GPT52ReasoningEffortSlider';
|
|
12
|
+
import ImageAspectRatioSelect from '@/features/ChatInput/ActionBar/Model/ImageAspectRatioSelect';
|
|
13
|
+
import ImageResolutionSlider from '@/features/ChatInput/ActionBar/Model/ImageResolutionSlider';
|
|
14
|
+
import ReasoningEffortSlider from '@/features/ChatInput/ActionBar/Model/ReasoningEffortSlider';
|
|
15
|
+
import ReasoningTokenSlider from '@/features/ChatInput/ActionBar/Model/ReasoningTokenSlider';
|
|
16
|
+
import TextVerbositySlider from '@/features/ChatInput/ActionBar/Model/TextVerbositySlider';
|
|
17
|
+
import ThinkingBudgetSlider from '@/features/ChatInput/ActionBar/Model/ThinkingBudgetSlider';
|
|
18
|
+
import ThinkingLevel2Slider from '@/features/ChatInput/ActionBar/Model/ThinkingLevel2Slider';
|
|
19
|
+
import ThinkingLevelSlider from '@/features/ChatInput/ActionBar/Model/ThinkingLevelSlider';
|
|
20
|
+
import ThinkingSlider from '@/features/ChatInput/ActionBar/Model/ThinkingSlider';
|
|
21
|
+
|
|
22
|
+
type ExtendParamsOption = {
|
|
23
|
+
hintKey: string;
|
|
24
|
+
key: ExtendParamsType;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const EXTEND_PARAMS_OPTIONS: ExtendParamsOption[] = [
|
|
28
|
+
{
|
|
29
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.disableContextCaching.hint',
|
|
30
|
+
key: 'disableContextCaching',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.enableReasoning.hint',
|
|
34
|
+
key: 'enableReasoning',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.reasoningBudgetToken.hint',
|
|
38
|
+
key: 'reasoningBudgetToken',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.reasoningEffort.hint',
|
|
42
|
+
key: 'reasoningEffort',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint',
|
|
46
|
+
key: 'gpt5ReasoningEffort',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint',
|
|
50
|
+
key: 'gpt5_1ReasoningEffort',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.gpt5_2ReasoningEffort.hint',
|
|
54
|
+
key: 'gpt5_2ReasoningEffort',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint',
|
|
58
|
+
key: 'gpt5_2ProReasoningEffort',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.textVerbosity.hint',
|
|
62
|
+
key: 'textVerbosity',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.thinking.hint',
|
|
66
|
+
key: 'thinking',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.thinkingBudget.hint',
|
|
70
|
+
key: 'thinkingBudget',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.thinkingLevel.hint',
|
|
74
|
+
key: 'thinkingLevel',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.thinkingLevel2.hint',
|
|
78
|
+
key: 'thinkingLevel2',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.urlContext.hint',
|
|
82
|
+
key: 'urlContext',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.imageAspectRatio.hint',
|
|
86
|
+
key: 'imageAspectRatio',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
hintKey: 'providerModels.item.modelConfig.extendParams.options.imageResolution.hint',
|
|
90
|
+
key: 'imageResolution',
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// Map variant keys to their base i18n title key (synced with ControlsForm.tsx)
|
|
95
|
+
// This allows reusing existing i18n translations instead of adding new ones
|
|
96
|
+
const TITLE_KEY_ALIASES: Partial<Record<ExtendParamsType, ExtendParamsType>> = {
|
|
97
|
+
gpt5ReasoningEffort: 'reasoningEffort',
|
|
98
|
+
gpt5_1ReasoningEffort: 'reasoningEffort',
|
|
99
|
+
gpt5_2ProReasoningEffort: 'reasoningEffort',
|
|
100
|
+
gpt5_2ReasoningEffort: 'reasoningEffort',
|
|
101
|
+
thinkingLevel2: 'thinkingLevel',
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
type PreviewMeta = {
|
|
105
|
+
labelOverride?: string;
|
|
106
|
+
labelSuffix?: string;
|
|
107
|
+
previewWidth?: number;
|
|
108
|
+
tag?: string;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const PREVIEW_META: Partial<Record<ExtendParamsType, PreviewMeta>> = {
|
|
112
|
+
disableContextCaching: { labelSuffix: ' (Claude)', previewWidth: 400 },
|
|
113
|
+
enableReasoning: { previewWidth: 300, tag: 'thinking.type' },
|
|
114
|
+
gpt5ReasoningEffort: { previewWidth: 300, tag: 'reasoning_effort' },
|
|
115
|
+
gpt5_1ReasoningEffort: { labelSuffix: ' (GPT-5.1)', previewWidth: 300, tag: 'reasoning_effort' },
|
|
116
|
+
gpt5_2ProReasoningEffort: {
|
|
117
|
+
labelSuffix: ' (GPT-5.2 Pro)',
|
|
118
|
+
previewWidth: 300,
|
|
119
|
+
tag: 'reasoning_effort',
|
|
120
|
+
},
|
|
121
|
+
gpt5_2ReasoningEffort: { labelSuffix: ' (GPT-5.2)', previewWidth: 300, tag: 'reasoning_effort' },
|
|
122
|
+
imageAspectRatio: { labelSuffix: '', previewWidth: 350, tag: 'aspect_ratio' },
|
|
123
|
+
imageResolution: { labelSuffix: '', previewWidth: 250, tag: 'resolution' },
|
|
124
|
+
reasoningBudgetToken: { previewWidth: 350, tag: 'thinking.budget_tokens' },
|
|
125
|
+
reasoningEffort: { previewWidth: 250, tag: 'reasoning_effort' },
|
|
126
|
+
textVerbosity: { labelSuffix: '', previewWidth: 250, tag: 'text_verbosity' },
|
|
127
|
+
thinking: { labelSuffix: ' (Doubao)', previewWidth: 300, tag: 'thinking.type' },
|
|
128
|
+
thinkingBudget: { labelSuffix: ' (Gemini)', previewWidth: 500, tag: 'thinkingBudget' },
|
|
129
|
+
thinkingLevel: { labelSuffix: ' (Gemini 3)', previewWidth: 280, tag: 'thinkingLevel' },
|
|
130
|
+
thinkingLevel2: { labelSuffix: ' (Gemini 3)', previewWidth: 200, tag: 'thinkingLevel' },
|
|
131
|
+
urlContext: { labelSuffix: ' (Gemini)', previewWidth: 400, tag: 'urlContext' },
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
type ExtendParamsDefinition = {
|
|
135
|
+
desc?: ReactNode;
|
|
136
|
+
hint: string;
|
|
137
|
+
key: ExtendParamsType;
|
|
138
|
+
label: string;
|
|
139
|
+
parameterTag?: string;
|
|
140
|
+
preview?: ReactNode;
|
|
141
|
+
previewWidth?: number;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
interface ExtendParamsSelectProps {
|
|
145
|
+
onChange?: (value: ExtendParamsType[] | undefined) => void;
|
|
146
|
+
value?: ExtendParamsType[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const PreviewContent = ({
|
|
150
|
+
desc,
|
|
151
|
+
hint,
|
|
152
|
+
label,
|
|
153
|
+
preview,
|
|
154
|
+
previewFallback,
|
|
155
|
+
parameterTag,
|
|
156
|
+
previewWidth,
|
|
157
|
+
}: {
|
|
158
|
+
desc?: ReactNode;
|
|
159
|
+
hint: string;
|
|
160
|
+
label: string;
|
|
161
|
+
parameterTag?: string;
|
|
162
|
+
preview?: ReactNode;
|
|
163
|
+
previewFallback: string;
|
|
164
|
+
previewWidth?: number;
|
|
165
|
+
}) => {
|
|
166
|
+
const { token } = theme.useToken();
|
|
167
|
+
const containerStyle = previewWidth
|
|
168
|
+
? { minWidth: previewWidth, width: previewWidth }
|
|
169
|
+
: { minWidth: 240 };
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<Flexbox gap={12} style={containerStyle}>
|
|
173
|
+
<Typography.Text style={{ whiteSpace: 'normal' }} type={'secondary'}>
|
|
174
|
+
{hint}
|
|
175
|
+
</Typography.Text>
|
|
176
|
+
<Flexbox gap={12}>
|
|
177
|
+
<Flexbox
|
|
178
|
+
gap={8}
|
|
179
|
+
style={{
|
|
180
|
+
background: token.colorBgElevated,
|
|
181
|
+
border: `1px solid ${token.colorBorderSecondary}`,
|
|
182
|
+
borderRadius: 10,
|
|
183
|
+
padding: 12,
|
|
184
|
+
width: previewWidth,
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
188
|
+
<Typography.Text strong>{label}</Typography.Text>
|
|
189
|
+
{parameterTag ? <Tag color={'cyan'}>{parameterTag}</Tag> : null}
|
|
190
|
+
</Flexbox>
|
|
191
|
+
{desc ? (
|
|
192
|
+
<Typography.Text style={{ fontSize: 12, whiteSpace: 'normal' }} type={'secondary'}>
|
|
193
|
+
{desc}
|
|
194
|
+
</Typography.Text>
|
|
195
|
+
) : null}
|
|
196
|
+
{preview ? (
|
|
197
|
+
<div style={{ pointerEvents: 'none', width: '100%' }}>{preview}</div>
|
|
198
|
+
) : (
|
|
199
|
+
<Typography.Text type={'secondary'}>{previewFallback}</Typography.Text>
|
|
200
|
+
)}
|
|
201
|
+
</Flexbox>
|
|
202
|
+
</Flexbox>
|
|
203
|
+
</Flexbox>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const ExtendParamsSelect = memo<ExtendParamsSelectProps>(({ value, onChange }) => {
|
|
208
|
+
const { t } = useTranslation('modelProvider');
|
|
209
|
+
const { t: tChat } = useTranslation('chat');
|
|
210
|
+
|
|
211
|
+
// Preview controls use controlled mode with default values (no store access)
|
|
212
|
+
const previewControls = useMemo<Partial<Record<ExtendParamsType, ReactNode>>>(
|
|
213
|
+
() => ({
|
|
214
|
+
disableContextCaching: <Switch checked disabled />,
|
|
215
|
+
enableReasoning: <Switch checked disabled />,
|
|
216
|
+
gpt5ReasoningEffort: <GPT5ReasoningEffortSlider value="medium" />,
|
|
217
|
+
gpt5_1ReasoningEffort: <GPT51ReasoningEffortSlider value="none" />,
|
|
218
|
+
gpt5_2ProReasoningEffort: <GPT52ProReasoningEffortSlider value="medium" />,
|
|
219
|
+
gpt5_2ReasoningEffort: <GPT52ReasoningEffortSlider value="none" />,
|
|
220
|
+
imageAspectRatio: <ImageAspectRatioSelect value="1:1" />,
|
|
221
|
+
imageResolution: <ImageResolutionSlider value="1K" />,
|
|
222
|
+
reasoningBudgetToken: <ReasoningTokenSlider defaultValue={1 * 1024} />,
|
|
223
|
+
reasoningEffort: <ReasoningEffortSlider value="medium" />,
|
|
224
|
+
textVerbosity: <TextVerbositySlider value="medium" />,
|
|
225
|
+
thinking: <ThinkingSlider value="auto" />,
|
|
226
|
+
thinkingBudget: <ThinkingBudgetSlider defaultValue={2 * 1024} />,
|
|
227
|
+
thinkingLevel: <ThinkingLevelSlider value="high" />,
|
|
228
|
+
thinkingLevel2: <ThinkingLevel2Slider value="high" />,
|
|
229
|
+
urlContext: <Switch checked disabled />,
|
|
230
|
+
}),
|
|
231
|
+
[],
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const descOverrides: Partial<Record<ExtendParamsType, ReactNode>> = {
|
|
235
|
+
disableContextCaching: (() => {
|
|
236
|
+
const original = tChat('extendParams.disableContextCaching.desc', { defaultValue: '' });
|
|
237
|
+
|
|
238
|
+
const sanitized = original.replace(/(<\d>.*?<\/\d>)/u, '');
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
sanitized || (
|
|
242
|
+
<Trans i18nKey={'extendParams.disableContextCaching.desc'} ns={'chat'}>
|
|
243
|
+
单条对话生成成本最高可降低 90%,响应速度提升 4 倍。开启后将自动禁用历史消息数限制
|
|
244
|
+
</Trans>
|
|
245
|
+
)
|
|
246
|
+
);
|
|
247
|
+
})(),
|
|
248
|
+
enableReasoning: (() => {
|
|
249
|
+
const original = tChat('extendParams.enableReasoning.desc', { defaultValue: '' });
|
|
250
|
+
|
|
251
|
+
const sanitized = original.replace(/(<\d>.*?<\/\d>)/u, '');
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
sanitized || (
|
|
255
|
+
<Trans i18nKey={'extendParams.enableReasoning.desc'} ns={'chat'}>
|
|
256
|
+
基于 Claude Thinking 机制限制,开启后将自动禁用历史消息数限制
|
|
257
|
+
</Trans>
|
|
258
|
+
)
|
|
259
|
+
);
|
|
260
|
+
})(),
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const previewFallback = String(
|
|
264
|
+
t('providerModels.item.modelConfig.extendParams.previewFallback', {
|
|
265
|
+
defaultValue: 'Preview unavailable',
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const definitions = useMemo<ExtendParamsDefinition[]>(() => {
|
|
270
|
+
return EXTEND_PARAMS_OPTIONS.map((item) => {
|
|
271
|
+
const descKey = `extendParams.${item.key}.desc`;
|
|
272
|
+
const rawDesc = tChat(descKey as any, { defaultValue: '' });
|
|
273
|
+
const normalizedDesc =
|
|
274
|
+
typeof rawDesc === 'string' && rawDesc !== '' && rawDesc !== descKey ? rawDesc : undefined;
|
|
275
|
+
const desc = descOverrides[item.key] ?? normalizedDesc;
|
|
276
|
+
const meta = PREVIEW_META[item.key];
|
|
277
|
+
// Use alias key for title if available (synced with ControlsForm.tsx)
|
|
278
|
+
const titleKey = TITLE_KEY_ALIASES[item.key] ?? item.key;
|
|
279
|
+
const baseLabel = String(
|
|
280
|
+
tChat(`extendParams.${titleKey}.title` as any, { defaultValue: item.key }),
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const label = meta?.labelOverride
|
|
284
|
+
? meta.labelOverride
|
|
285
|
+
: meta?.labelSuffix
|
|
286
|
+
? `${baseLabel}${meta.labelSuffix}`
|
|
287
|
+
: baseLabel;
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
desc,
|
|
291
|
+
hint: String(t(item.hintKey as any)),
|
|
292
|
+
key: item.key,
|
|
293
|
+
label,
|
|
294
|
+
parameterTag: meta?.tag,
|
|
295
|
+
preview: previewControls[item.key],
|
|
296
|
+
previewWidth: meta?.previewWidth,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
}, [previewControls, t, tChat]);
|
|
300
|
+
|
|
301
|
+
const definitionMap = useMemo(() => {
|
|
302
|
+
return new Map(definitions.map((item) => [item.key, item]));
|
|
303
|
+
}, [definitions]);
|
|
304
|
+
|
|
305
|
+
const options = useMemo(
|
|
306
|
+
() =>
|
|
307
|
+
definitions.map((item) => ({
|
|
308
|
+
label: item.label,
|
|
309
|
+
value: item.key,
|
|
310
|
+
})),
|
|
311
|
+
[definitions],
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const placeholder = String(t('providerModels.item.modelConfig.extendParams.placeholder'));
|
|
315
|
+
const handleChange = (val: ExtendParamsType[]) => {
|
|
316
|
+
if (!Array.isArray(val) || val.length === 0) {
|
|
317
|
+
onChange?.(undefined);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const filtered = val.filter((item) => definitionMap.has(item));
|
|
322
|
+
onChange?.(filtered.length ? filtered : undefined);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<Flexbox gap={8}>
|
|
327
|
+
<Select
|
|
328
|
+
allowClear
|
|
329
|
+
mode={'multiple'}
|
|
330
|
+
onChange={(val) => handleChange(val as ExtendParamsType[])}
|
|
331
|
+
optionRender={(option) => {
|
|
332
|
+
const def = definitionMap.get(option.value as ExtendParamsType);
|
|
333
|
+
if (!def) return option.label;
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
<Popover
|
|
337
|
+
content={
|
|
338
|
+
<PreviewContent
|
|
339
|
+
desc={def.desc}
|
|
340
|
+
hint={def.hint}
|
|
341
|
+
label={def.label}
|
|
342
|
+
parameterTag={def.parameterTag}
|
|
343
|
+
preview={def.preview}
|
|
344
|
+
previewFallback={previewFallback}
|
|
345
|
+
previewWidth={def.previewWidth}
|
|
346
|
+
/>
|
|
347
|
+
}
|
|
348
|
+
placement={'right'}
|
|
349
|
+
>
|
|
350
|
+
<Flexbox gap={4}>
|
|
351
|
+
<Typography.Text>{def.label}</Typography.Text>
|
|
352
|
+
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
|
|
353
|
+
{def.hint}
|
|
354
|
+
</Typography.Text>
|
|
355
|
+
</Flexbox>
|
|
356
|
+
</Popover>
|
|
357
|
+
);
|
|
358
|
+
}}
|
|
359
|
+
options={options}
|
|
360
|
+
placeholder={placeholder}
|
|
361
|
+
popupMatchSelectWidth={false}
|
|
362
|
+
style={{ width: '100%' }}
|
|
363
|
+
value={value}
|
|
364
|
+
/>
|
|
365
|
+
{value && value.length > 0 && (
|
|
366
|
+
<Space size={[8, 8]} wrap>
|
|
367
|
+
{value.map((key) => {
|
|
368
|
+
const def = definitionMap.get(key);
|
|
369
|
+
if (!def) return null;
|
|
370
|
+
return (
|
|
371
|
+
<Popover
|
|
372
|
+
content={
|
|
373
|
+
<PreviewContent
|
|
374
|
+
desc={def.desc}
|
|
375
|
+
hint={def.hint}
|
|
376
|
+
label={def.label}
|
|
377
|
+
parameterTag={def.parameterTag}
|
|
378
|
+
preview={def.preview}
|
|
379
|
+
previewFallback={previewFallback}
|
|
380
|
+
previewWidth={def.previewWidth}
|
|
381
|
+
/>
|
|
382
|
+
}
|
|
383
|
+
key={key}
|
|
384
|
+
placement={'top'}
|
|
385
|
+
>
|
|
386
|
+
<Tag bordered={false} color={'processing'}>
|
|
387
|
+
{def.label}
|
|
388
|
+
</Tag>
|
|
389
|
+
</Popover>
|
|
390
|
+
);
|
|
391
|
+
})}
|
|
392
|
+
</Space>
|
|
393
|
+
)}
|
|
394
|
+
</Flexbox>
|
|
395
|
+
);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
export default ExtendParamsSelect;
|
package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/Form.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Input
|
|
2
|
-
import { Form, type FormInstance, Select } from 'antd';
|
|
1
|
+
import { Input } from '@lobehub/ui';
|
|
2
|
+
import { Checkbox, Form, type FormInstance, Select } from 'antd';
|
|
3
3
|
import { type AiModelType } from 'model-bank';
|
|
4
4
|
import { memo, useEffect, useMemo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
@@ -8,6 +8,8 @@ import MaxTokenSlider from '@/components/MaxTokenSlider';
|
|
|
8
8
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
|
9
9
|
import { type ChatModelCard } from '@/types/llm';
|
|
10
10
|
|
|
11
|
+
import ExtendParamsSelect from './ExtendParamsSelect';
|
|
12
|
+
|
|
11
13
|
interface ModelConfigFormProps {
|
|
12
14
|
idEditable?: boolean;
|
|
13
15
|
initialValues?: ChatModelCard;
|
|
@@ -102,6 +104,13 @@ const ModelConfigForm = memo<ModelConfigFormProps>(
|
|
|
102
104
|
>
|
|
103
105
|
<MaxTokenSlider />
|
|
104
106
|
</Form.Item>
|
|
107
|
+
<Form.Item
|
|
108
|
+
extra={t('providerModels.item.modelConfig.extendParams.extra')}
|
|
109
|
+
label={t('providerModels.item.modelConfig.extendParams.title')}
|
|
110
|
+
name={['settings', 'extendParams']}
|
|
111
|
+
>
|
|
112
|
+
<ExtendParamsSelect />
|
|
113
|
+
</Form.Item>
|
|
105
114
|
<Form.Item
|
|
106
115
|
extra={t('providerModels.item.modelConfig.functionCall.extra')}
|
|
107
116
|
label={t('providerModels.item.modelConfig.functionCall.title')}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// Import the constant directly for testing
|
|
4
|
+
// We'll need to test the TITLE_KEY_ALIASES logic
|
|
5
|
+
|
|
6
|
+
describe('ExtendParamsSelect', () => {
|
|
7
|
+
describe('TITLE_KEY_ALIASES mapping', () => {
|
|
8
|
+
// This mapping should be synced with ControlsForm.tsx
|
|
9
|
+
const TITLE_KEY_ALIASES: Record<string, string> = {
|
|
10
|
+
gpt5ReasoningEffort: 'reasoningEffort',
|
|
11
|
+
gpt5_1ReasoningEffort: 'reasoningEffort',
|
|
12
|
+
gpt5_2ProReasoningEffort: 'reasoningEffort',
|
|
13
|
+
gpt5_2ReasoningEffort: 'reasoningEffort',
|
|
14
|
+
thinkingLevel2: 'thinkingLevel',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
it('should map GPT5 variants to reasoningEffort', () => {
|
|
18
|
+
expect(TITLE_KEY_ALIASES['gpt5ReasoningEffort']).toBe('reasoningEffort');
|
|
19
|
+
expect(TITLE_KEY_ALIASES['gpt5_1ReasoningEffort']).toBe('reasoningEffort');
|
|
20
|
+
expect(TITLE_KEY_ALIASES['gpt5_2ReasoningEffort']).toBe('reasoningEffort');
|
|
21
|
+
expect(TITLE_KEY_ALIASES['gpt5_2ProReasoningEffort']).toBe('reasoningEffort');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should map thinkingLevel2 to thinkingLevel', () => {
|
|
25
|
+
expect(TITLE_KEY_ALIASES['thinkingLevel2']).toBe('thinkingLevel');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return undefined for keys without aliases', () => {
|
|
29
|
+
expect(TITLE_KEY_ALIASES['reasoningEffort']).toBeUndefined();
|
|
30
|
+
expect(TITLE_KEY_ALIASES['thinkingLevel']).toBeUndefined();
|
|
31
|
+
expect(TITLE_KEY_ALIASES['thinking']).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('title key resolution logic', () => {
|
|
36
|
+
const TITLE_KEY_ALIASES: Record<string, string> = {
|
|
37
|
+
gpt5ReasoningEffort: 'reasoningEffort',
|
|
38
|
+
gpt5_1ReasoningEffort: 'reasoningEffort',
|
|
39
|
+
gpt5_2ProReasoningEffort: 'reasoningEffort',
|
|
40
|
+
gpt5_2ReasoningEffort: 'reasoningEffort',
|
|
41
|
+
thinkingLevel2: 'thinkingLevel',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getTitleKey = (key: string): string => {
|
|
45
|
+
return TITLE_KEY_ALIASES[key] ?? key;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
it('should return the alias key when available', () => {
|
|
49
|
+
expect(getTitleKey('gpt5ReasoningEffort')).toBe('reasoningEffort');
|
|
50
|
+
expect(getTitleKey('thinkingLevel2')).toBe('thinkingLevel');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should return the original key when no alias exists', () => {
|
|
54
|
+
expect(getTitleKey('reasoningEffort')).toBe('reasoningEffort');
|
|
55
|
+
expect(getTitleKey('thinking')).toBe('thinking');
|
|
56
|
+
expect(getTitleKey('textVerbosity')).toBe('textVerbosity');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -178,7 +178,7 @@ const ControlsForm = memo(() => {
|
|
|
178
178
|
},
|
|
179
179
|
{
|
|
180
180
|
children: <ThinkingBudgetSlider />,
|
|
181
|
-
label: t('extendParams.
|
|
181
|
+
label: t('extendParams.thinkingBudget.title'),
|
|
182
182
|
layout: 'vertical',
|
|
183
183
|
minWidth: 460,
|
|
184
184
|
name: 'thinkingBudget',
|
|
@@ -1,60 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Slider } from 'antd';
|
|
3
|
-
import { memo, useCallback } from 'react';
|
|
1
|
+
import { type CreatedLevelSliderProps, createLevelSliderComponent } from './createLevelSlider';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const GPT51_REASONING_EFFORT_LEVELS = ['none', 'low', 'medium', 'high'] as const;
|
|
4
|
+
type GPT51ReasoningEffort = (typeof GPT51_REASONING_EFFORT_LEVELS)[number];
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
import { useUpdateAgentConfig } from '../../hooks/useUpdateAgentConfig';
|
|
6
|
+
export type GPT51ReasoningEffortSliderProps = CreatedLevelSliderProps<GPT51ReasoningEffort>;
|
|
10
7
|
|
|
11
|
-
const GPT51ReasoningEffortSlider =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const gpt5_1ReasoningEffort = config.gpt5_1ReasoningEffort || 'none'; // Default to 'none' if not set
|
|
17
|
-
|
|
18
|
-
const marks = {
|
|
19
|
-
0: 'none',
|
|
20
|
-
1: 'low',
|
|
21
|
-
2: 'medium',
|
|
22
|
-
3: 'high',
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const effortValues = ['none', 'low', 'medium', 'high'];
|
|
26
|
-
const indexValue = effortValues.indexOf(gpt5_1ReasoningEffort);
|
|
27
|
-
const currentValue = indexValue === -1 ? 0 : indexValue;
|
|
28
|
-
|
|
29
|
-
const updateGPT51ReasoningEffort = useCallback(
|
|
30
|
-
(value: number) => {
|
|
31
|
-
const effort = effortValues[value] as 'none' | 'low' | 'medium' | 'high';
|
|
32
|
-
updateAgentChatConfig({ gpt5_1ReasoningEffort: effort });
|
|
33
|
-
},
|
|
34
|
-
[updateAgentChatConfig],
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<Flexbox
|
|
39
|
-
align={'center'}
|
|
40
|
-
gap={12}
|
|
41
|
-
horizontal
|
|
42
|
-
paddingInline={'0 20px'}
|
|
43
|
-
style={{ minWidth: 200, width: '100%' }}
|
|
44
|
-
>
|
|
45
|
-
<Flexbox flex={1}>
|
|
46
|
-
<Slider
|
|
47
|
-
marks={marks}
|
|
48
|
-
max={3}
|
|
49
|
-
min={0}
|
|
50
|
-
onChange={updateGPT51ReasoningEffort}
|
|
51
|
-
step={1}
|
|
52
|
-
tooltip={{ open: false }}
|
|
53
|
-
value={currentValue}
|
|
54
|
-
/>
|
|
55
|
-
</Flexbox>
|
|
56
|
-
</Flexbox>
|
|
57
|
-
);
|
|
8
|
+
const GPT51ReasoningEffortSlider = createLevelSliderComponent<GPT51ReasoningEffort>({
|
|
9
|
+
configKey: 'gpt5_1ReasoningEffort',
|
|
10
|
+
defaultValue: 'none',
|
|
11
|
+
levels: GPT51_REASONING_EFFORT_LEVELS,
|
|
12
|
+
style: { minWidth: 200 },
|
|
58
13
|
});
|
|
59
14
|
|
|
60
15
|
export default GPT51ReasoningEffortSlider;
|
|
@@ -1,59 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Slider } from 'antd';
|
|
3
|
-
import { memo, useCallback } from 'react';
|
|
1
|
+
import { type CreatedLevelSliderProps, createLevelSliderComponent } from './createLevelSlider';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const GPT52_PRO_REASONING_EFFORT_LEVELS = ['medium', 'high', 'xhigh'] as const;
|
|
4
|
+
type GPT52ProReasoningEffort = (typeof GPT52_PRO_REASONING_EFFORT_LEVELS)[number];
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
import { useUpdateAgentConfig } from '../../hooks/useUpdateAgentConfig';
|
|
6
|
+
export type GPT52ProReasoningEffortSliderProps = CreatedLevelSliderProps<GPT52ProReasoningEffort>;
|
|
10
7
|
|
|
11
|
-
const GPT52ProReasoningEffortSlider =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const gpt5_2ProReasoningEffort = config.gpt5_2ProReasoningEffort || 'medium';
|
|
17
|
-
|
|
18
|
-
const marks = {
|
|
19
|
-
0: 'medium',
|
|
20
|
-
1: 'high',
|
|
21
|
-
2: 'xhigh',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const effortValues = ['medium', 'high', 'xhigh'];
|
|
25
|
-
const indexValue = effortValues.indexOf(gpt5_2ProReasoningEffort);
|
|
26
|
-
const currentValue = indexValue === -1 ? 0 : indexValue;
|
|
27
|
-
|
|
28
|
-
const updateGPT52ProReasoningEffort = useCallback(
|
|
29
|
-
(value: number) => {
|
|
30
|
-
const effort = effortValues[value] as 'medium' | 'high' | 'xhigh';
|
|
31
|
-
updateAgentChatConfig({ gpt5_2ProReasoningEffort: effort });
|
|
32
|
-
},
|
|
33
|
-
[updateAgentChatConfig],
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<Flexbox
|
|
38
|
-
align={'center'}
|
|
39
|
-
gap={12}
|
|
40
|
-
horizontal
|
|
41
|
-
paddingInline={'0 20px'}
|
|
42
|
-
style={{ minWidth: 160, width: '100%' }}
|
|
43
|
-
>
|
|
44
|
-
<Flexbox flex={1}>
|
|
45
|
-
<Slider
|
|
46
|
-
marks={marks}
|
|
47
|
-
max={2}
|
|
48
|
-
min={0}
|
|
49
|
-
onChange={updateGPT52ProReasoningEffort}
|
|
50
|
-
step={1}
|
|
51
|
-
tooltip={{ open: false }}
|
|
52
|
-
value={currentValue}
|
|
53
|
-
/>
|
|
54
|
-
</Flexbox>
|
|
55
|
-
</Flexbox>
|
|
56
|
-
);
|
|
8
|
+
const GPT52ProReasoningEffortSlider = createLevelSliderComponent<GPT52ProReasoningEffort>({
|
|
9
|
+
configKey: 'gpt5_2ProReasoningEffort',
|
|
10
|
+
defaultValue: 'medium',
|
|
11
|
+
levels: GPT52_PRO_REASONING_EFFORT_LEVELS,
|
|
12
|
+
style: { minWidth: 160 },
|
|
57
13
|
});
|
|
58
14
|
|
|
59
15
|
export default GPT52ProReasoningEffortSlider;
|