Skip to content

Commit e8ed847

Browse files
committed
✨ feat: support update model config
1 parent 62d6bb7 commit e8ed847

File tree

15 files changed

+365
-175
lines changed

15 files changed

+365
-175
lines changed

src/app/settings/llm/OpenAI/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const LLM = memo(() => {
6969
children: (
7070
<ProviderModelListSelect
7171
placeholder={t('llm.openai.customModelName.placeholder')}
72-
provider={'openai'}
72+
provider={'openAI'}
7373
/>
7474
),
7575
desc: t('llm.openai.customModelName.desc'),

src/app/settings/llm/components/ProviderModelList/CustomModelOption.tsx

+49-45
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { ActionIcon } from '@lobehub/ui';
22
import { App, Typography } from 'antd';
3+
import isEqual from 'fast-deep-equal';
34
import { LucideSettings, LucideTrash2 } from 'lucide-react';
4-
import { memo, useState } from 'react';
5+
import { memo } from 'react';
56
import { useTranslation } from 'react-i18next';
67
import { Flexbox } from 'react-layout-kit';
78

89
import ModelIcon from '@/components/ModelIcon';
10+
import { ModelInfoTags } from '@/components/ModelSelect';
911
import { useGlobalStore } from '@/store/global';
12+
import { modelConfigSelectors } from '@/store/global/slices/settings/selectors';
1013
import { GlobalLLMProviderKey } from '@/types/settings';
1114

12-
import ModelConfigModal from './ModelConfigModal';
13-
1415
interface CustomModelOptionProps {
1516
id: string;
1617
provider: GlobalLLMProviderKey;
@@ -21,57 +22,60 @@ const CustomModelOption = memo<CustomModelOptionProps>(({ id, provider }) => {
2122
const { t: s } = useTranslation('setting');
2223
const { modal } = App.useApp();
2324

24-
const [open, setOpen] = useState(true);
25-
const [dispatchCustomModelCards] = useGlobalStore((s) => [s.dispatchCustomModelCards]);
25+
const [dispatchCustomModelCards, toggleEditingCustomModelCard] = useGlobalStore((s) => [
26+
s.dispatchCustomModelCards,
27+
s.toggleEditingCustomModelCard,
28+
]);
29+
const modelCard = useGlobalStore(
30+
modelConfigSelectors.getCustomModelCardById({ id, provider }),
31+
isEqual,
32+
);
2633

2734
return (
28-
<>
29-
<Flexbox align={'center'} distribution={'space-between'} gap={8} horizontal>
35+
<Flexbox align={'center'} distribution={'space-between'} gap={8} horizontal>
36+
<Flexbox align={'center'} gap={8} horizontal>
37+
<ModelIcon model={id} size={32} />
3038
<Flexbox>
31-
<ModelIcon model={id} size={32} />
32-
<Flexbox>
33-
<Flexbox align={'center'} gap={8} horizontal>
34-
{id}
35-
{/*<ModelInfoTags id={id} isCustom />*/}
36-
</Flexbox>
37-
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
38-
{id}
39-
</Typography.Text>
39+
<Flexbox align={'center'} gap={8} horizontal>
40+
{modelCard?.displayName || id}
41+
<ModelInfoTags id={id} {...modelCard} isCustom />
4042
</Flexbox>
43+
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
44+
{id}
45+
</Typography.Text>
4146
</Flexbox>
47+
</Flexbox>
4248

43-
<Flexbox horizontal>
44-
<ActionIcon
45-
icon={LucideSettings}
46-
onClick={async (e) => {
47-
e.stopPropagation();
48-
setOpen(true);
49-
}}
50-
title={s('llm.customModelCards.config')}
51-
/>
52-
<ActionIcon
53-
icon={LucideTrash2}
54-
onClick={async (e) => {
55-
e.stopPropagation();
56-
e.preventDefault();
49+
<Flexbox horizontal>
50+
<ActionIcon
51+
icon={LucideSettings}
52+
onClick={async (e) => {
53+
e.stopPropagation();
54+
toggleEditingCustomModelCard({ id, provider });
55+
}}
56+
title={s('llm.customModelCards.config')}
57+
/>
58+
<ActionIcon
59+
icon={LucideTrash2}
60+
onClick={async (e) => {
61+
e.stopPropagation();
62+
e.preventDefault();
5763

58-
const isConfirm = await modal.confirm({
59-
centered: true,
60-
content: s('llm.customModelCards.confirmDelete'),
61-
okButtonProps: { danger: true },
62-
type: 'warning',
63-
});
64+
const isConfirm = await modal.confirm({
65+
centered: true,
66+
content: s('llm.customModelCards.confirmDelete'),
67+
okButtonProps: { danger: true },
68+
type: 'warning',
69+
});
6470

65-
if (isConfirm) {
66-
dispatchCustomModelCards(provider, { id, type: 'delete' });
67-
}
68-
}}
69-
title={t('delete')}
70-
/>
71-
</Flexbox>
71+
if (isConfirm) {
72+
dispatchCustomModelCards(provider, { id, type: 'delete' });
73+
}
74+
}}
75+
title={t('delete')}
76+
/>
7277
</Flexbox>
73-
<ModelConfigModal onOpenChange={setOpen} open={open} provider={provider} />
74-
</>
78+
</Flexbox>
7579
);
7680
});
7781

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { InputNumber, Slider, SliderSingleProps } from 'antd';
2+
import { memo } from 'react';
3+
import { Flexbox } from 'react-layout-kit';
4+
import useMergeState from 'use-merge-value';
5+
6+
const exponent = (num: number) => Math.log2(num);
7+
const getRealValue = (num: number) => Math.round(Math.pow(2, num));
8+
9+
const marks: SliderSingleProps['marks'] = {
10+
[exponent(1)]: '1k',
11+
[exponent(2)]: '2k',
12+
[exponent(4)]: '4k',
13+
[exponent(8)]: '8k',
14+
[exponent(16)]: '16k',
15+
[exponent(32)]: '32k',
16+
[exponent(64)]: '64k',
17+
[exponent(128)]: '128k',
18+
[exponent(200)]: '200k',
19+
[exponent(1000)]: '1M',
20+
};
21+
22+
interface MaxTokenSliderProps {
23+
defaultValue?: number;
24+
onChange?: (value: number) => void;
25+
value?: number;
26+
}
27+
28+
const MaxTokenSlider = memo<MaxTokenSliderProps>(({ value, onChange, defaultValue }) => {
29+
const [token, setTokens] = useMergeState(0, {
30+
defaultValue,
31+
onChange,
32+
value: value,
33+
});
34+
35+
const [powValue, setPowValue] = useMergeState(0, {
36+
defaultValue: exponent(typeof defaultValue === 'undefined' ? 0 : defaultValue / 1000),
37+
value: exponent(typeof value === 'undefined' ? 0 : value / 1000),
38+
});
39+
40+
const updateWithPowValue = (value: number) => {
41+
setPowValue(value);
42+
43+
setTokens(getRealValue(value) * 1024);
44+
};
45+
const updateWithRealValue = (value: number) => {
46+
setTokens(value);
47+
48+
setPowValue(exponent(value / 1024));
49+
};
50+
51+
return (
52+
<Flexbox align={'center'} gap={12} horizontal>
53+
<Flexbox flex={1}>
54+
<Slider
55+
marks={marks}
56+
max={exponent(1000)}
57+
min={0}
58+
onChange={updateWithPowValue}
59+
step={1}
60+
tooltip={{
61+
formatter: (x) => {
62+
if (typeof x === 'undefined') return;
63+
64+
const value = getRealValue(x);
65+
66+
if (value < 1000) return value.toFixed(0) + 'K';
67+
68+
return (value / 1000).toFixed(0) + 'M';
69+
},
70+
}}
71+
value={powValue}
72+
/>
73+
</Flexbox>
74+
<div>
75+
<InputNumber
76+
onChange={(e) => {
77+
if (!e) return;
78+
79+
updateWithRealValue(e);
80+
}}
81+
step={1024}
82+
value={token}
83+
/>
84+
</div>
85+
</Flexbox>
86+
);
87+
});
88+
export default MaxTokenSlider;

src/app/settings/llm/components/ProviderModelList/ModelConfigModal.tsx

+87-60
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,108 @@
1-
import { Modal, SliderWithInput } from '@lobehub/ui';
1+
import { Modal } from '@lobehub/ui';
22
import { Checkbox, Form, Input } from 'antd';
3+
import isEqual from 'fast-deep-equal';
34
import { memo } from 'react';
45
import { useTranslation } from 'react-i18next';
56

6-
import { GlobalLLMProviderKey } from '@/types/settings';
7+
import { useGlobalStore } from '@/store/global';
8+
import { modelConfigSelectors } from '@/store/global/slices/settings/selectors';
79

8-
interface ModelConfigModalProps {
9-
id: string;
10-
onOpenChange: (open: boolean) => void;
11-
open?: boolean;
12-
provider: GlobalLLMProviderKey;
13-
}
14-
const ModelConfigModal = memo<ModelConfigModalProps>(({ open, id, onOpenChange }) => {
10+
import MaxTokenSlider from './MaxTokenSlider';
11+
12+
const ModelConfigModal = memo(() => {
1513
const [formInstance] = Form.useForm();
1614
const { t } = useTranslation('setting');
1715

16+
const [open, id, provider, dispatchCustomModelCards, toggleEditingCustomModelCard] =
17+
useGlobalStore((s) => [
18+
!!s.editingCustomCardModel,
19+
s.editingCustomCardModel?.id,
20+
s.editingCustomCardModel?.provider,
21+
s.dispatchCustomModelCards,
22+
s.toggleEditingCustomModelCard,
23+
]);
24+
25+
const modelCard = useGlobalStore(
26+
modelConfigSelectors.getCustomModelCardById({ id, provider }),
27+
isEqual,
28+
);
29+
30+
const closeModal = () => {
31+
toggleEditingCustomModelCard(undefined);
32+
};
33+
1834
return (
1935
<Modal
36+
destroyOnClose
2037
maskClosable
2138
onCancel={() => {
22-
onOpenChange(false);
39+
closeModal();
40+
}}
41+
onOk={() => {
42+
if (!provider || !id) return;
43+
const data = formInstance.getFieldsValue();
44+
45+
dispatchCustomModelCards(provider as any, { id, type: 'update', value: data });
46+
47+
closeModal();
2348
}}
2449
open={open}
2550
title={t('llm.customModelCards.modelConfig.modalTitle')}
2651
>
27-
<Form
28-
colon={false}
29-
form={formInstance}
30-
labelCol={{ offset: 0, span: 4 }}
31-
style={{ marginTop: 16 }}
32-
wrapperCol={{ offset: 1, span: 19 }}
52+
<div
53+
onClick={(e) => {
54+
e.stopPropagation();
55+
}}
56+
onKeyDown={(e) => {
57+
e.stopPropagation();
58+
}}
3359
>
34-
<Form.Item label={t('llm.customModelCards.modelConfig.id.title')} name={'id'}>
35-
<Input placeholder={t('llm.customModelCards.modelConfig.id.placeholder')} />
36-
</Form.Item>
37-
<Form.Item
38-
label={t('llm.customModelCards.modelConfig.displayName.title')}
39-
name={'displayName'}
40-
>
41-
<Input placeholder={t('llm.customModelCards.modelConfig.displayName.placeholder')} />
42-
</Form.Item>
43-
<Form.Item label={t('llm.customModelCards.modelConfig.tokens.title')} name={'tokens'}>
44-
<SliderWithInput
45-
marks={{
46-
100_000: '100k',
47-
128_000: '128k',
48-
16_385: '16k',
49-
200_000: '200k',
50-
32_768: '32k',
51-
4096: '4k',
52-
}}
53-
max={200_000}
54-
min={0}
55-
/>
56-
</Form.Item>
57-
<Form.Item
58-
extra={t('llm.customModelCards.modelConfig.functionCall.extra')}
59-
label={t('llm.customModelCards.modelConfig.functionCall.title')}
60-
name={'functionCall'}
61-
>
62-
<Checkbox />
63-
</Form.Item>
64-
<Form.Item
65-
extra={t('llm.customModelCards.modelConfig.vision.extra')}
66-
label={t('llm.customModelCards.modelConfig.vision.title')}
67-
name={'vision'}
68-
>
69-
<Checkbox />
70-
</Form.Item>
71-
<Form.Item
72-
extra={t('llm.customModelCards.modelConfig.files.extra')}
73-
label={t('llm.customModelCards.modelConfig.files.title')}
74-
name={'files'}
60+
<Form
61+
colon={false}
62+
form={formInstance}
63+
initialValues={modelCard}
64+
labelCol={{ span: 4 }}
65+
style={{ marginTop: 16 }}
66+
wrapperCol={{ offset: 1, span: 18 }}
7567
>
76-
<Checkbox />
77-
</Form.Item>
78-
</Form>
68+
<Form.Item label={t('llm.customModelCards.modelConfig.id.title')} name={'id'}>
69+
<Input placeholder={t('llm.customModelCards.modelConfig.id.placeholder')} />
70+
</Form.Item>
71+
<Form.Item
72+
label={t('llm.customModelCards.modelConfig.displayName.title')}
73+
name={'displayName'}
74+
>
75+
<Input placeholder={t('llm.customModelCards.modelConfig.displayName.placeholder')} />
76+
</Form.Item>
77+
<Form.Item label={t('llm.customModelCards.modelConfig.tokens.title')} name={'tokens'}>
78+
<MaxTokenSlider />
79+
</Form.Item>
80+
<Form.Item
81+
extra={t('llm.customModelCards.modelConfig.functionCall.extra')}
82+
label={t('llm.customModelCards.modelConfig.functionCall.title')}
83+
name={'functionCall'}
84+
valuePropName={'checked'}
85+
>
86+
<Checkbox />
87+
</Form.Item>
88+
<Form.Item
89+
extra={t('llm.customModelCards.modelConfig.vision.extra')}
90+
label={t('llm.customModelCards.modelConfig.vision.title')}
91+
name={'vision'}
92+
valuePropName={'checked'}
93+
>
94+
<Checkbox />
95+
</Form.Item>
96+
<Form.Item
97+
extra={t('llm.customModelCards.modelConfig.files.extra')}
98+
label={t('llm.customModelCards.modelConfig.files.title')}
99+
name={'files'}
100+
valuePropName={'checked'}
101+
>
102+
<Checkbox />
103+
</Form.Item>
104+
</Form>
105+
</div>
79106
</Modal>
80107
);
81108
});

0 commit comments

Comments
 (0)