@lobehub/lobehub 2.0.0-next.262 → 2.0.0-next.263

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/zh-CN/chat.json +1 -0
  4. package/locales/zh-CN/modelProvider.json +20 -0
  5. package/package.json +1 -1
  6. package/packages/database/src/models/aiModel.ts +2 -0
  7. package/packages/database/src/repositories/aiInfra/index.test.ts +41 -1
  8. package/packages/database/src/repositories/aiInfra/index.ts +3 -1
  9. package/packages/model-runtime/src/providers/openrouter/index.test.ts +9 -55
  10. package/packages/model-runtime/src/providers/openrouter/index.ts +47 -27
  11. package/packages/model-runtime/src/providers/openrouter/type.ts +16 -28
  12. package/packages/model-runtime/src/providers/vercelaigateway/index.test.ts +6 -6
  13. package/packages/model-runtime/src/providers/vercelaigateway/index.ts +54 -11
  14. package/packages/model-runtime/src/utils/modelParse.test.ts +185 -3
  15. package/packages/model-runtime/src/utils/modelParse.ts +108 -1
  16. package/packages/types/src/llm.ts +3 -1
  17. package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/ExtendParamsSelect.tsx +398 -0
  18. package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/Form.tsx +11 -2
  19. package/src/app/[variants]/(main)/settings/provider/features/ModelList/CreateNewModelModal/__tests__/ExtendParamsSelect.test.tsx +59 -0
  20. package/src/features/ChatInput/ActionBar/Model/ControlsForm.tsx +1 -1
  21. package/src/features/ChatInput/ActionBar/Model/GPT51ReasoningEffortSlider.tsx +9 -54
  22. package/src/features/ChatInput/ActionBar/Model/GPT52ProReasoningEffortSlider.tsx +9 -53
  23. package/src/features/ChatInput/ActionBar/Model/GPT52ReasoningEffortSlider.tsx +9 -55
  24. package/src/features/ChatInput/ActionBar/Model/GPT5ReasoningEffortSlider.tsx +9 -54
  25. package/src/features/ChatInput/ActionBar/Model/ImageAspectRatioSelect.tsx +50 -16
  26. package/src/features/ChatInput/ActionBar/Model/ImageResolutionSlider.tsx +7 -53
  27. package/src/features/ChatInput/ActionBar/Model/LevelSlider.tsx +92 -0
  28. package/src/features/ChatInput/ActionBar/Model/ReasoningEffortSlider.tsx +9 -53
  29. package/src/features/ChatInput/ActionBar/Model/TextVerbositySlider.tsx +9 -53
  30. package/src/features/ChatInput/ActionBar/Model/ThinkingLevel2Slider.tsx +9 -52
  31. package/src/features/ChatInput/ActionBar/Model/ThinkingLevelSlider.tsx +9 -54
  32. package/src/features/ChatInput/ActionBar/Model/ThinkingSlider.tsx +20 -56
  33. package/src/features/ChatInput/ActionBar/Model/__tests__/createLevelSlider.test.tsx +126 -0
  34. package/src/features/ChatInput/ActionBar/Model/createLevelSlider.tsx +105 -0
  35. package/src/locales/default/chat.ts +1 -0
  36. package/src/locales/default/modelProvider.ts +38 -0
@@ -1,59 +1,23 @@
1
- import { Flexbox } from '@lobehub/ui';
2
- import { Slider } from 'antd';
3
- import { memo, useCallback } from 'react';
4
-
5
- import { useAgentStore } from '@/store/agent';
6
- import { chatConfigByIdSelectors } from '@/store/agent/selectors';
7
-
8
- import { useAgentId } from '../../hooks/useAgentId';
9
- import { useUpdateAgentConfig } from '../../hooks/useUpdateAgentConfig';
10
-
11
- const ThinkingSlider = memo(() => {
12
- const agentId = useAgentId();
13
- const { updateAgentChatConfig } = useUpdateAgentConfig();
14
- const config = useAgentStore((s) => chatConfigByIdSelectors.getChatConfigById(agentId)(s));
15
-
16
- const thinking = config.thinking || 'auto'; // Default to 'auto' if not set
17
-
18
- const marks = {
19
- 0: 'OFF',
20
- 1: 'Auto',
21
- 2: 'ON',
22
- };
23
-
24
- const thinkingValues = ['disabled', 'auto', 'enabled'];
25
- const indexValue = thinkingValues.indexOf(thinking);
26
- const currentValue = indexValue === -1 ? 1 : indexValue;
27
-
28
- const updateThinking = useCallback(
29
- (value: number) => {
30
- const thinkingMode = thinkingValues[value] as 'disabled' | 'auto' | 'enabled';
31
- updateAgentChatConfig({ thinking: thinkingMode });
32
- },
33
- [updateAgentChatConfig],
34
- );
35
-
36
- return (
37
- <Flexbox
38
- align={'center'}
39
- gap={12}
40
- horizontal
41
- paddingInline={'0 20px'}
42
- style={{ minWidth: 200, width: '100%' }}
43
- >
44
- <Flexbox flex={1}>
45
- <Slider
46
- marks={marks}
47
- max={2}
48
- min={0}
49
- onChange={updateThinking}
50
- step={1}
51
- tooltip={{ open: false }}
52
- value={currentValue}
53
- />
54
- </Flexbox>
55
- </Flexbox>
56
- );
1
+ import { type CreatedLevelSliderProps, createLevelSliderComponent } from './createLevelSlider';
2
+
3
+ const THINKING_MODES = ['disabled', 'auto', 'enabled'] as const;
4
+ type ThinkingMode = (typeof THINKING_MODES)[number];
5
+
6
+ // Display marks for the slider
7
+ const THINKING_MARKS = {
8
+ 0: 'OFF',
9
+ 1: 'Auto',
10
+ 2: 'ON',
11
+ };
12
+
13
+ export type ThinkingSliderProps = CreatedLevelSliderProps<ThinkingMode>;
14
+
15
+ const ThinkingSlider = createLevelSliderComponent<ThinkingMode>({
16
+ configKey: 'thinking',
17
+ defaultValue: 'auto',
18
+ levels: THINKING_MODES,
19
+ marks: THINKING_MARKS,
20
+ style: { minWidth: 200 },
57
21
  });
58
22
 
59
23
  export default ThinkingSlider;
@@ -0,0 +1,126 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+
4
+ import { createLevelSliderComponent } from '../createLevelSlider';
5
+
6
+ // Mock the store hooks - they should NOT be called in controlled mode
7
+ vi.mock('@/store/agent', () => ({
8
+ useAgentStore: vi.fn(() => {
9
+ throw new Error('useAgentStore should not be called in controlled mode');
10
+ }),
11
+ }));
12
+
13
+ vi.mock('../../../hooks/useAgentId', () => ({
14
+ useAgentId: vi.fn(() => {
15
+ throw new Error('useAgentId should not be called in controlled mode');
16
+ }),
17
+ }));
18
+
19
+ vi.mock('../../../hooks/useUpdateAgentConfig', () => ({
20
+ useUpdateAgentConfig: vi.fn(() => {
21
+ throw new Error('useUpdateAgentConfig should not be called in controlled mode');
22
+ }),
23
+ }));
24
+
25
+ const TEST_LEVELS = ['low', 'medium', 'high'] as const;
26
+ type TestLevel = (typeof TEST_LEVELS)[number];
27
+
28
+ describe('createLevelSliderComponent', () => {
29
+ describe('controlled mode (with value prop)', () => {
30
+ it('should NOT call store hooks when value prop is provided', () => {
31
+ const TestSlider = createLevelSliderComponent<TestLevel>({
32
+ configKey: 'reasoningEffort',
33
+ defaultValue: 'medium',
34
+ levels: TEST_LEVELS,
35
+ });
36
+
37
+ // This should NOT throw - if it throws, it means store hooks were called
38
+ expect(() => {
39
+ render(<TestSlider value="high" />);
40
+ }).not.toThrow();
41
+ });
42
+
43
+ it('should NOT call store hooks when onChange prop is provided', () => {
44
+ const TestSlider = createLevelSliderComponent<TestLevel>({
45
+ configKey: 'reasoningEffort',
46
+ defaultValue: 'medium',
47
+ levels: TEST_LEVELS,
48
+ });
49
+
50
+ const mockOnChange = vi.fn();
51
+
52
+ // This should NOT throw - if it throws, it means store hooks were called
53
+ expect(() => {
54
+ render(<TestSlider onChange={mockOnChange} />);
55
+ }).not.toThrow();
56
+ });
57
+
58
+ it('should render with the controlled value', () => {
59
+ const TestSlider = createLevelSliderComponent<TestLevel>({
60
+ configKey: 'reasoningEffort',
61
+ defaultValue: 'medium',
62
+ levels: TEST_LEVELS,
63
+ });
64
+
65
+ render(<TestSlider value="high" />);
66
+
67
+ // The slider should show the marks
68
+ expect(screen.getByText('low')).toBeInTheDocument();
69
+ expect(screen.getByText('medium')).toBeInTheDocument();
70
+ expect(screen.getByText('high')).toBeInTheDocument();
71
+ });
72
+
73
+ it('should use defaultValue when value is not provided but onChange is', () => {
74
+ const TestSlider = createLevelSliderComponent<TestLevel>({
75
+ configKey: 'reasoningEffort',
76
+ defaultValue: 'medium',
77
+ levels: TEST_LEVELS,
78
+ });
79
+
80
+ const mockOnChange = vi.fn();
81
+
82
+ // Should not throw and should render
83
+ expect(() => {
84
+ render(<TestSlider onChange={mockOnChange} />);
85
+ }).not.toThrow();
86
+ });
87
+ });
88
+
89
+ describe('factory configuration', () => {
90
+ it('should create slider with custom marks', () => {
91
+ const customMarks = {
92
+ 0: 'OFF',
93
+ 1: 'Auto',
94
+ 2: 'ON',
95
+ };
96
+
97
+ const TestSlider = createLevelSliderComponent<TestLevel>({
98
+ configKey: 'thinking',
99
+ defaultValue: 'medium',
100
+ levels: TEST_LEVELS,
101
+ marks: customMarks,
102
+ });
103
+
104
+ render(<TestSlider value="medium" />);
105
+
106
+ expect(screen.getByText('OFF')).toBeInTheDocument();
107
+ expect(screen.getByText('Auto')).toBeInTheDocument();
108
+ expect(screen.getByText('ON')).toBeInTheDocument();
109
+ });
110
+
111
+ it('should apply custom style', () => {
112
+ const TestSlider = createLevelSliderComponent<TestLevel>({
113
+ configKey: 'reasoningEffort',
114
+ defaultValue: 'medium',
115
+ levels: TEST_LEVELS,
116
+ style: { minWidth: 300 },
117
+ });
118
+
119
+ const { container } = render(<TestSlider value="medium" />);
120
+
121
+ // The outer Flexbox should have the custom style merged
122
+ const flexbox = container.firstChild as HTMLElement;
123
+ expect(flexbox).toHaveStyle({ minWidth: '300px' });
124
+ });
125
+ });
126
+ });
@@ -0,0 +1,105 @@
1
+ 'use client';
2
+
3
+ import { type LobeAgentChatConfig } from '@lobechat/types';
4
+ import { type SliderSingleProps } from 'antd/es/slider';
5
+ import { type CSSProperties, memo } from 'react';
6
+
7
+ import { useAgentStore } from '@/store/agent';
8
+ import { chatConfigByIdSelectors } from '@/store/agent/selectors';
9
+
10
+ import { useAgentId } from '../../hooks/useAgentId';
11
+ import { useUpdateAgentConfig } from '../../hooks/useUpdateAgentConfig';
12
+ import LevelSlider from './LevelSlider';
13
+
14
+ export interface LevelSliderConfig<T extends string> {
15
+ /**
16
+ * The key in LobeAgentChatConfig to read/write
17
+ */
18
+ configKey: keyof LobeAgentChatConfig;
19
+ /**
20
+ * Default value when no value is provided
21
+ */
22
+ defaultValue: T;
23
+ /**
24
+ * Ordered array of level values (left to right on slider)
25
+ */
26
+ levels: readonly T[];
27
+ /**
28
+ * Optional custom marks for the slider
29
+ */
30
+ marks?: SliderSingleProps['marks'];
31
+ /**
32
+ * Optional style for the slider container
33
+ */
34
+ style?: CSSProperties;
35
+ }
36
+
37
+ export interface CreatedLevelSliderProps<T extends string> {
38
+ defaultValue?: T;
39
+ onChange?: (value: T) => void;
40
+ value?: T;
41
+ }
42
+
43
+ /**
44
+ * Factory function to create a level slider component that supports both
45
+ * controlled mode (for previews without store access) and uncontrolled mode
46
+ * (reading/writing to agent store).
47
+ */
48
+ export function createLevelSliderComponent<T extends string>(config: LevelSliderConfig<T>) {
49
+ const { levels, configKey, defaultValue, marks, style } = config;
50
+
51
+ // Inner pure UI component - no store hooks, safe for preview
52
+ const LevelSliderInner = memo<{
53
+ defaultValue: T;
54
+ onChange: (_v: T) => void;
55
+ value: T;
56
+ }>(({ value, onChange, defaultValue: dv }) => (
57
+ <LevelSlider<T>
58
+ defaultValue={dv}
59
+ levels={levels}
60
+ marks={marks}
61
+ onChange={onChange}
62
+ style={style}
63
+ value={value}
64
+ />
65
+ ));
66
+
67
+ // Store-connected component - uses agent store hooks
68
+ const LevelSliderWithStore = memo<{ defaultValue: T }>(({ defaultValue: dv }) => {
69
+ const agentId = useAgentId();
70
+ const { updateAgentChatConfig } = useUpdateAgentConfig();
71
+ const agentConfig = useAgentStore((s) => chatConfigByIdSelectors.getChatConfigById(agentId)(s));
72
+
73
+ const storeValue = (agentConfig[configKey] as T) || dv;
74
+
75
+ const handleChange = (newValue: T) => {
76
+ updateAgentChatConfig({ [configKey]: newValue });
77
+ };
78
+
79
+ return <LevelSliderInner defaultValue={dv} onChange={handleChange} value={storeValue} />;
80
+ });
81
+
82
+ // Main exported component - chooses between controlled and store mode
83
+ const CreatedLevelSlider = memo<CreatedLevelSliderProps<T>>(
84
+ ({ value: controlledValue, onChange: controlledOnChange, defaultValue: propDefaultValue }) => {
85
+ const dv = propDefaultValue ?? defaultValue;
86
+ const isControlled = controlledValue !== undefined || controlledOnChange !== undefined;
87
+
88
+ if (isControlled) {
89
+ // Controlled mode: use props only, no store access
90
+ return (
91
+ <LevelSliderInner
92
+ defaultValue={dv}
93
+ onChange={controlledOnChange ?? (() => {})}
94
+ value={controlledValue ?? dv}
95
+ />
96
+ );
97
+ }
98
+
99
+ // Uncontrolled mode: use store
100
+ return <LevelSliderWithStore defaultValue={dv} />;
101
+ },
102
+ );
103
+
104
+ return CreatedLevelSlider;
105
+ }
@@ -78,6 +78,7 @@ export default {
78
78
  'extendParams.reasoningEffort.title': 'Reasoning Intensity',
79
79
  'extendParams.textVerbosity.title': 'Output Text Detail Level',
80
80
  'extendParams.thinking.title': 'Deep Thinking Switch',
81
+ 'extendParams.thinkingBudget.title': 'Thinking Budget',
81
82
  'extendParams.thinkingLevel.title': 'Level of Thinking',
82
83
  'extendParams.title': 'Model Extension Features',
83
84
  'extendParams.urlContext.desc':
@@ -220,6 +220,44 @@ export default {
220
220
  'providerModels.item.modelConfig.displayName.placeholder':
221
221
  'Please enter the display name of the model, e.g., ChatGPT, GPT-4, etc.',
222
222
  'providerModels.item.modelConfig.displayName.title': 'Model Display Name',
223
+ 'providerModels.item.modelConfig.extendParams.extra':
224
+ 'Choose extended parameters supported by the model. Hover an option to preview controls. Incorrect configs may cause request failures.',
225
+ 'providerModels.item.modelConfig.extendParams.options.disableContextCaching.hint':
226
+ 'For Claude models; can lower cost and speed up responses.',
227
+ 'providerModels.item.modelConfig.extendParams.options.enableReasoning.hint':
228
+ 'For Claude, DeepSeek and other reasoning models; unlock deeper thinking.',
229
+ 'providerModels.item.modelConfig.extendParams.options.gpt5ReasoningEffort.hint':
230
+ 'For GPT-5 series; controls reasoning intensity.',
231
+ 'providerModels.item.modelConfig.extendParams.options.gpt5_1ReasoningEffort.hint':
232
+ 'For GPT-5.1 series; controls reasoning intensity.',
233
+ 'providerModels.item.modelConfig.extendParams.options.gpt5_2ProReasoningEffort.hint':
234
+ 'For GPT-5.2 Pro series; controls reasoning intensity.',
235
+ 'providerModels.item.modelConfig.extendParams.options.gpt5_2ReasoningEffort.hint':
236
+ 'For GPT-5.2 series; controls reasoning intensity.',
237
+ 'providerModels.item.modelConfig.extendParams.options.imageAspectRatio.hint':
238
+ 'For Gemini image generation models; controls aspect ratio of generated images.',
239
+ 'providerModels.item.modelConfig.extendParams.options.imageResolution.hint':
240
+ 'For Gemini 3 image generation models; controls resolution of generated images.',
241
+ 'providerModels.item.modelConfig.extendParams.options.reasoningBudgetToken.hint':
242
+ 'For Claude, Qwen3 and similar; controls token budget for reasoning.',
243
+ 'providerModels.item.modelConfig.extendParams.options.reasoningEffort.hint':
244
+ 'For OpenAI and other reasoning-capable models; controls reasoning effort.',
245
+ 'providerModels.item.modelConfig.extendParams.options.textVerbosity.hint':
246
+ 'For GPT-5+ series; controls output verbosity.',
247
+ 'providerModels.item.modelConfig.extendParams.options.thinking.hint':
248
+ 'For some Doubao models; allow model to decide whether to think deeply.',
249
+ 'providerModels.item.modelConfig.extendParams.options.thinkingBudget.hint':
250
+ 'For Gemini series; controls thinking budget.',
251
+ 'providerModels.item.modelConfig.extendParams.options.thinkingLevel.hint':
252
+ 'For Gemini 3 Flash Preview models; controls thinking depth.',
253
+ 'providerModels.item.modelConfig.extendParams.options.thinkingLevel2.hint':
254
+ 'For Gemini 3 Pro Preview models; controls thinking depth.',
255
+ 'providerModels.item.modelConfig.extendParams.options.urlContext.hint':
256
+ 'For Gemini series; supports providing URL context.',
257
+ 'providerModels.item.modelConfig.extendParams.placeholder':
258
+ 'Select extended parameters to enable',
259
+ 'providerModels.item.modelConfig.extendParams.previewFallback': 'Preview unavailable',
260
+ 'providerModels.item.modelConfig.extendParams.title': 'Extended Parameters',
223
261
  'providerModels.item.modelConfig.files.extra':
224
262
  'The current file upload implementation is just a hack solution, limited to self-experimentation. Please wait for complete file upload capabilities in future implementations.',
225
263
  'providerModels.item.modelConfig.files.title': 'File Upload Support',