@lobehub/chat 0.161.9 → 0.161.11
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/package.json +2 -1
- package/src/app/(main)/_layout/Desktop.tsx +2 -2
- package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +5 -2
- package/src/app/(main)/settings/llm/components/ProviderModelList/CustomModelOption.tsx +6 -7
- package/src/app/(main)/settings/llm/components/ProviderModelList/{ModelConfigModal.tsx → ModelConfigModal/Form.tsx} +19 -63
- package/src/app/(main)/settings/llm/components/ProviderModelList/ModelConfigModal/index.tsx +78 -0
- package/src/app/(main)/settings/llm/components/ProviderModelList/Option.tsx +35 -11
- package/src/app/(main)/settings/llm/components/ProviderModelList/index.tsx +15 -18
- package/src/app/layout.tsx +2 -0
- package/src/components/ModelProviderIcon/index.tsx +2 -2
- package/src/components/ModelSelect/index.tsx +5 -14
- package/src/const/layoutTokens.ts +1 -0
- package/src/features/PWAInstall/index.tsx +22 -0
- package/src/features/User/UserPanel/PanelContent.tsx +1 -1
- package/src/hooks/usePWAInstall.test.ts +78 -0
- package/src/hooks/usePWAInstall.ts +23 -2
- package/src/hooks/usePlatform.test.ts +82 -0
- package/src/hooks/usePlatform.ts +19 -2
- package/src/hooks/useSyncData.ts +3 -1
- package/src/layout/GlobalProvider/StoreInitialization.tsx +17 -9
- package/src/layout/GlobalProvider/index.tsx +1 -1
- package/src/locales/default/components.ts +1 -0
- package/src/services/message/client.test.ts +0 -24
- package/src/services/message/client.ts +0 -5
- package/src/services/message/type.ts +0 -1
- package/src/services/user/client.test.ts +100 -0
- package/src/services/user/client.ts +16 -14
- package/src/services/user/index.ts +0 -2
- package/src/services/user/type.ts +2 -4
- package/src/store/user/initialState.ts +10 -1
- package/src/store/user/selectors.ts +3 -7
- package/src/store/user/slices/auth/action.test.ts +5 -87
- package/src/store/user/slices/auth/action.ts +3 -58
- package/src/store/user/slices/auth/initialState.ts +2 -1
- package/src/store/user/slices/common/action.test.ts +196 -20
- package/src/store/user/slices/common/action.ts +55 -26
- package/src/store/user/slices/common/initialState.ts +9 -0
- package/src/store/user/slices/modelList/action.test.ts +363 -0
- package/src/store/user/slices/{settings/actions/llm.ts → modelList/action.ts} +66 -60
- package/src/store/user/slices/modelList/initialState.ts +15 -0
- package/src/store/user/slices/modelList/selectors/index.ts +2 -0
- package/src/store/user/slices/{settings → modelList}/selectors/modelConfig.test.ts +3 -2
- package/src/store/user/slices/{settings → modelList}/selectors/modelConfig.ts +1 -1
- package/src/store/user/slices/{settings → modelList}/selectors/modelProvider.test.ts +7 -7
- package/src/store/user/slices/{settings → modelList}/selectors/modelProvider.ts +2 -4
- package/src/store/user/slices/preference/action.test.ts +0 -52
- package/src/store/user/slices/preference/action.ts +1 -17
- package/src/store/user/slices/preference/initialState.ts +0 -5
- package/src/store/user/slices/preference/selectors.test.ts +2 -2
- package/src/store/user/slices/preference/selectors.ts +1 -1
- package/src/store/user/slices/settings/{actions/general.ts → action.ts} +5 -5
- package/src/store/user/slices/settings/initialState.ts +0 -12
- package/src/store/user/slices/settings/selectors/index.ts +0 -3
- package/src/store/user/slices/sync/action.test.ts +19 -5
- package/src/store/user/slices/sync/action.ts +9 -6
- package/src/store/user/slices/{settings/selectors/sync.ts → sync/selectors.ts} +2 -2
- package/src/store/user/store.ts +5 -2
- package/src/types/serverConfig.ts +3 -1
- package/src/types/user/index.ts +13 -0
- package/src/utils/parseModels.test.ts +121 -1
- package/src/utils/parseModels.ts +9 -4
- package/src/utils/platform.test.ts +83 -0
- package/src/utils/platform.ts +33 -2
- package/src/hooks/useIsPWA.ts +0 -13
- package/src/store/user/slices/settings/actions/index.ts +0 -18
- package/src/store/user/slices/settings/actions/llm.test.ts +0 -136
- package/src/utils/matchMedia.ts +0 -10
- /package/src/app/(main)/settings/llm/components/ProviderModelList/{MaxTokenSlider.tsx → ModelConfigModal/MaxTokenSlider.tsx} +0 -0
- /package/src/store/user/slices/{settings → modelList}/reducers/customModelCard.test.ts +0 -0
- /package/src/store/user/slices/{settings → modelList}/reducers/customModelCard.ts +0 -0
- /package/src/store/user/slices/settings/{actions/general.test.ts → action.test.ts} +0 -0
- /package/src/store/user/slices/settings/selectors/__snapshots__/{selectors.test.ts.snap → settings.test.ts.snap} +0 -0
- /package/src/store/user/slices/settings/selectors/{selectors.test.ts → settings.test.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.161.11](https://github.com/lobehub/lobe-chat/compare/v0.161.10...v0.161.11)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2024-05-23**</sup>
|
|
8
|
+
|
|
9
|
+
#### 💄 Styles
|
|
10
|
+
|
|
11
|
+
- **misc**: Improve PWA install guide.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Styles
|
|
19
|
+
|
|
20
|
+
- **misc**: Improve PWA install guide, closes [#2617](https://github.com/lobehub/lobe-chat/issues/2617) ([7fee545](https://github.com/lobehub/lobe-chat/commit/7fee545))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
### [Version 0.161.10](https://github.com/lobehub/lobe-chat/compare/v0.161.9...v0.161.10)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2024-05-23**</sup>
|
|
33
|
+
|
|
34
|
+
#### 🐛 Bug Fixes
|
|
35
|
+
|
|
36
|
+
- **misc**: Refactor user store and fix custom model list form.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's fixed
|
|
44
|
+
|
|
45
|
+
- **misc**: Refactor user store and fix custom model list form, closes [#2620](https://github.com/lobehub/lobe-chat/issues/2620) ([81ea886](https://github.com/lobehub/lobe-chat/commit/81ea886))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
### [Version 0.161.9](https://github.com/lobehub/lobe-chat/compare/v0.161.8...v0.161.9)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2024-05-23**</sup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.161.
|
|
3
|
+
"version": "0.161.11",
|
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"@clerk/themes": "^2.1.6",
|
|
95
95
|
"@google/generative-ai": "^0.11.3",
|
|
96
96
|
"@icons-pack/react-simple-icons": "^9.5.0",
|
|
97
|
+
"@khmyznikov/pwa-install": "^0.3.9",
|
|
97
98
|
"@lobehub/chat-plugin-sdk": "latest",
|
|
98
99
|
"@lobehub/chat-plugins-gateway": "latest",
|
|
99
100
|
"@lobehub/icons": "^1.22.0",
|
|
@@ -4,12 +4,12 @@ import { useTheme } from 'antd-style';
|
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { usePlatform } from '@/hooks/usePlatform';
|
|
8
8
|
|
|
9
9
|
import { LayoutProps } from './type';
|
|
10
10
|
|
|
11
11
|
const Layout = memo<LayoutProps>(({ children, nav }) => {
|
|
12
|
-
const isPWA =
|
|
12
|
+
const { isPWA } = usePlatform();
|
|
13
13
|
const theme = useTheme();
|
|
14
14
|
|
|
15
15
|
return (
|
|
@@ -50,6 +50,8 @@ interface ProviderConfigProps {
|
|
|
50
50
|
canDeactivate?: boolean;
|
|
51
51
|
checkModel?: string;
|
|
52
52
|
checkerItem?: FormItemProps;
|
|
53
|
+
className?: string;
|
|
54
|
+
hideSwitch?: boolean;
|
|
53
55
|
modelList?: {
|
|
54
56
|
azureDeployName?: boolean;
|
|
55
57
|
notFoundContent?: ReactNode;
|
|
@@ -81,11 +83,12 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
|
81
83
|
checkerItem,
|
|
82
84
|
modelList,
|
|
83
85
|
showBrowserRequest,
|
|
86
|
+
className,
|
|
84
87
|
}) => {
|
|
85
88
|
const { t } = useTranslation('setting');
|
|
86
89
|
const { t: modelT } = useTranslation('modelProvider');
|
|
87
90
|
const [form] = Form.useForm();
|
|
88
|
-
const { styles } = useStyles();
|
|
91
|
+
const { cx, styles } = useStyles();
|
|
89
92
|
const [
|
|
90
93
|
toggleProviderEnabled,
|
|
91
94
|
setSettings,
|
|
@@ -192,7 +195,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
|
|
|
192
195
|
|
|
193
196
|
return (
|
|
194
197
|
<Form
|
|
195
|
-
className={styles.form}
|
|
198
|
+
className={cx(styles.form, className)}
|
|
196
199
|
form={form}
|
|
197
200
|
items={[model]}
|
|
198
201
|
onValuesChange={debounce(setSettings, 100)}
|
|
@@ -71,18 +71,17 @@ const CustomModelOption = memo<CustomModelOptionProps>(({ id, provider }) => {
|
|
|
71
71
|
e.stopPropagation();
|
|
72
72
|
e.preventDefault();
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
await modal.confirm({
|
|
75
75
|
centered: true,
|
|
76
76
|
content: s('llm.customModelCards.confirmDelete'),
|
|
77
77
|
okButtonProps: { danger: true },
|
|
78
|
+
onOk: async () => {
|
|
79
|
+
// delete model and deactivate id
|
|
80
|
+
await dispatchCustomModelCards(provider, { id, type: 'delete' });
|
|
81
|
+
await removeEnabledModels(provider, id);
|
|
82
|
+
},
|
|
78
83
|
type: 'warning',
|
|
79
84
|
});
|
|
80
|
-
|
|
81
|
-
// delete model and deactive id
|
|
82
|
-
if (isConfirm) {
|
|
83
|
-
await dispatchCustomModelCards(provider, { id, type: 'delete' });
|
|
84
|
-
await removeEnabledModels(provider, id);
|
|
85
|
-
}
|
|
86
85
|
}}
|
|
87
86
|
title={t('delete')}
|
|
88
87
|
/>
|
|
@@ -1,71 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import isEqual from 'fast-deep-equal';
|
|
4
|
-
import { memo } from 'react';
|
|
1
|
+
import { Checkbox, Form, FormInstance, Input } from 'antd';
|
|
2
|
+
import { memo, useEffect } from 'react';
|
|
5
3
|
import { useTranslation } from 'react-i18next';
|
|
6
4
|
|
|
7
|
-
import {
|
|
8
|
-
import { modelConfigSelectors } from '@/store/user/selectors';
|
|
5
|
+
import { ChatModelCard } from '@/types/llm';
|
|
9
6
|
|
|
10
7
|
import MaxTokenSlider from './MaxTokenSlider';
|
|
11
8
|
|
|
12
|
-
interface
|
|
13
|
-
|
|
9
|
+
interface ModelConfigFormProps {
|
|
10
|
+
initialValues?: ChatModelCard;
|
|
11
|
+
onFormInstanceReady: (instance: FormInstance) => void;
|
|
14
12
|
showAzureDeployName?: boolean;
|
|
15
13
|
}
|
|
16
|
-
const ModelConfigModal = memo<ModelConfigModalProps>(({ showAzureDeployName, provider }) => {
|
|
17
|
-
const [formInstance] = Form.useForm();
|
|
18
|
-
const { t } = useTranslation('setting');
|
|
19
|
-
const { t: tc } = useTranslation('common');
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
s.editingCustomCardModel?.id,
|
|
25
|
-
s.editingCustomCardModel?.provider,
|
|
26
|
-
s.dispatchCustomModelCards,
|
|
27
|
-
s.toggleEditingCustomModelCard,
|
|
28
|
-
]);
|
|
15
|
+
const ModelConfigForm = memo<ModelConfigFormProps>(
|
|
16
|
+
({ showAzureDeployName, onFormInstanceReady, initialValues }) => {
|
|
17
|
+
const { t } = useTranslation('setting');
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
modelConfigSelectors.getCustomModelCard({ id, provider: editingProvider }),
|
|
32
|
-
isEqual,
|
|
33
|
-
);
|
|
19
|
+
const [formInstance] = Form.useForm();
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
onFormInstanceReady(formInstance);
|
|
23
|
+
}, []);
|
|
38
24
|
|
|
39
|
-
|
|
40
|
-
<Modal
|
|
41
|
-
destroyOnClose
|
|
42
|
-
footer={[
|
|
43
|
-
<Button key="cancel" onClick={closeModal}>
|
|
44
|
-
{tc('cancel')}
|
|
45
|
-
</Button>,
|
|
46
|
-
|
|
47
|
-
<Button
|
|
48
|
-
key="ok"
|
|
49
|
-
onClick={() => {
|
|
50
|
-
if (!editingProvider || !id) return;
|
|
51
|
-
const data = formInstance.getFieldsValue();
|
|
52
|
-
|
|
53
|
-
dispatchCustomModelCards(editingProvider as any, { id, type: 'update', value: data });
|
|
54
|
-
|
|
55
|
-
closeModal();
|
|
56
|
-
}}
|
|
57
|
-
style={{ marginInlineStart: '16px' }}
|
|
58
|
-
type="primary"
|
|
59
|
-
>
|
|
60
|
-
{tc('ok')}
|
|
61
|
-
</Button>,
|
|
62
|
-
]}
|
|
63
|
-
maskClosable
|
|
64
|
-
onCancel={closeModal}
|
|
65
|
-
open={open}
|
|
66
|
-
title={t('llm.customModelCards.modelConfig.modalTitle')}
|
|
67
|
-
zIndex={1051} // Select is 1050
|
|
68
|
-
>
|
|
25
|
+
return (
|
|
69
26
|
<div
|
|
70
27
|
onClick={(e) => {
|
|
71
28
|
e.stopPropagation();
|
|
@@ -77,9 +34,8 @@ const ModelConfigModal = memo<ModelConfigModalProps>(({ showAzureDeployName, pro
|
|
|
77
34
|
<Form
|
|
78
35
|
colon={false}
|
|
79
36
|
form={formInstance}
|
|
80
|
-
initialValues={
|
|
37
|
+
initialValues={initialValues}
|
|
81
38
|
labelCol={{ span: 4 }}
|
|
82
|
-
preserve={false}
|
|
83
39
|
style={{ marginTop: 16 }}
|
|
84
40
|
wrapperCol={{ offset: 1, span: 18 }}
|
|
85
41
|
>
|
|
@@ -136,7 +92,7 @@ const ModelConfigModal = memo<ModelConfigModalProps>(({ showAzureDeployName, pro
|
|
|
136
92
|
</Form.Item>
|
|
137
93
|
</Form>
|
|
138
94
|
</div>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
export default
|
|
95
|
+
);
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
export default ModelConfigForm;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Modal } from '@lobehub/ui';
|
|
2
|
+
import { Button, FormInstance } from 'antd';
|
|
3
|
+
import isEqual from 'fast-deep-equal';
|
|
4
|
+
import { memo, useState } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
import { useUserStore } from '@/store/user';
|
|
8
|
+
import { modelConfigSelectors } from '@/store/user/slices/modelList/selectors';
|
|
9
|
+
|
|
10
|
+
import ModelConfigForm from './Form';
|
|
11
|
+
|
|
12
|
+
interface ModelConfigModalProps {
|
|
13
|
+
provider?: string;
|
|
14
|
+
showAzureDeployName?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ModelConfigModal = memo<ModelConfigModalProps>(({ showAzureDeployName, provider }) => {
|
|
18
|
+
const { t } = useTranslation('setting');
|
|
19
|
+
const { t: tc } = useTranslation('common');
|
|
20
|
+
const [formInstance, setFormInstance] = useState<FormInstance>();
|
|
21
|
+
|
|
22
|
+
const [open, id, editingProvider, dispatchCustomModelCards, toggleEditingCustomModelCard] =
|
|
23
|
+
useUserStore((s) => [
|
|
24
|
+
!!s.editingCustomCardModel && provider === s.editingCustomCardModel?.provider,
|
|
25
|
+
s.editingCustomCardModel?.id,
|
|
26
|
+
s.editingCustomCardModel?.provider,
|
|
27
|
+
s.dispatchCustomModelCards,
|
|
28
|
+
s.toggleEditingCustomModelCard,
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const modelCard = useUserStore(
|
|
32
|
+
modelConfigSelectors.getCustomModelCard({ id, provider: editingProvider }),
|
|
33
|
+
isEqual,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const closeModal = () => {
|
|
37
|
+
toggleEditingCustomModelCard(undefined);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Modal
|
|
42
|
+
destroyOnClose
|
|
43
|
+
footer={[
|
|
44
|
+
<Button key="cancel" onClick={closeModal}>
|
|
45
|
+
{tc('cancel')}
|
|
46
|
+
</Button>,
|
|
47
|
+
|
|
48
|
+
<Button
|
|
49
|
+
key="ok"
|
|
50
|
+
onClick={() => {
|
|
51
|
+
if (!editingProvider || !id || !formInstance) return;
|
|
52
|
+
const data = formInstance.getFieldsValue();
|
|
53
|
+
|
|
54
|
+
dispatchCustomModelCards(editingProvider as any, { id, type: 'update', value: data });
|
|
55
|
+
|
|
56
|
+
closeModal();
|
|
57
|
+
}}
|
|
58
|
+
style={{ marginInlineStart: '16px' }}
|
|
59
|
+
type="primary"
|
|
60
|
+
>
|
|
61
|
+
{tc('ok')}
|
|
62
|
+
</Button>,
|
|
63
|
+
]}
|
|
64
|
+
maskClosable
|
|
65
|
+
onCancel={closeModal}
|
|
66
|
+
open={open}
|
|
67
|
+
title={t('llm.customModelCards.modelConfig.modalTitle')}
|
|
68
|
+
zIndex={1251} // Select is 1150
|
|
69
|
+
>
|
|
70
|
+
<ModelConfigForm
|
|
71
|
+
initialValues={modelCard}
|
|
72
|
+
onFormInstanceReady={setFormInstance}
|
|
73
|
+
showAzureDeployName={showAzureDeployName}
|
|
74
|
+
/>
|
|
75
|
+
</Modal>
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
export default ModelConfigModal;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { ActionIcon, Tooltip } from '@lobehub/ui';
|
|
1
2
|
import { Typography } from 'antd';
|
|
3
|
+
import { useTheme } from 'antd-style';
|
|
2
4
|
import isEqual from 'fast-deep-equal';
|
|
5
|
+
import { Recycle } from 'lucide-react';
|
|
3
6
|
import { memo } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
4
8
|
import { Flexbox } from 'react-layout-kit';
|
|
5
9
|
|
|
6
10
|
import ModelIcon from '@/components/ModelIcon';
|
|
@@ -16,25 +20,45 @@ interface OptionRenderProps {
|
|
|
16
20
|
id: string;
|
|
17
21
|
isAzure?: boolean;
|
|
18
22
|
provider: GlobalLLMProviderKey;
|
|
23
|
+
removed?: boolean;
|
|
19
24
|
}
|
|
20
|
-
const OptionRender = memo<OptionRenderProps>(({ displayName, id, provider, isAzure }) => {
|
|
25
|
+
const OptionRender = memo<OptionRenderProps>(({ displayName, id, provider, isAzure, removed }) => {
|
|
21
26
|
const model = useUserStore((s) => modelProviderSelectors.getModelCardById(id)(s), isEqual);
|
|
22
|
-
|
|
27
|
+
const { t } = useTranslation('components');
|
|
28
|
+
const theme = useTheme();
|
|
23
29
|
// if there is isCustom, it means it is a user defined custom model
|
|
24
30
|
if (model?.isCustom || isAzure) return <CustomModelOption id={id} provider={provider} />;
|
|
25
31
|
|
|
26
32
|
return (
|
|
27
|
-
<Flexbox
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
<Flexbox
|
|
34
|
+
align={'center'}
|
|
35
|
+
gap={8}
|
|
36
|
+
horizontal
|
|
37
|
+
justify={'space-between'}
|
|
38
|
+
style={{ paddingInlineEnd: 8 }}
|
|
39
|
+
>
|
|
40
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
41
|
+
<ModelIcon model={id} size={32} />
|
|
42
|
+
<Flexbox>
|
|
43
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
44
|
+
{displayName}
|
|
45
|
+
<ModelInfoTags directionReverse placement={'top'} {...model!} />
|
|
46
|
+
</Flexbox>
|
|
47
|
+
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
|
|
48
|
+
{id}
|
|
49
|
+
</Typography.Text>
|
|
33
50
|
</Flexbox>
|
|
34
|
-
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
|
|
35
|
-
{id}
|
|
36
|
-
</Typography.Text>
|
|
37
51
|
</Flexbox>
|
|
52
|
+
{removed && (
|
|
53
|
+
<Tooltip
|
|
54
|
+
overlayStyle={{ maxWidth: 300 }}
|
|
55
|
+
placement={'top'}
|
|
56
|
+
style={{ pointerEvents: 'none' }}
|
|
57
|
+
title={t('ModelSelect.removed')}
|
|
58
|
+
>
|
|
59
|
+
<ActionIcon icon={Recycle} style={{ color: theme.colorWarning }} />
|
|
60
|
+
</Tooltip>
|
|
61
|
+
)}
|
|
38
62
|
</Flexbox>
|
|
39
63
|
);
|
|
40
64
|
});
|
|
@@ -51,10 +51,9 @@ const ProviderModelListSelect = memo<CustomModelSelectProps>(
|
|
|
51
51
|
({ showModelFetcher = false, provider, showAzureDeployName, notFoundContent, placeholder }) => {
|
|
52
52
|
const { t } = useTranslation('common');
|
|
53
53
|
const { t: transSetting } = useTranslation('setting');
|
|
54
|
-
const [setModelProviderConfig,
|
|
54
|
+
const [setModelProviderConfig, updateEnabledModels] = useUserStore((s) => [
|
|
55
55
|
s.setModelProviderConfig,
|
|
56
|
-
s.
|
|
57
|
-
s.useFetchProviderModelList,
|
|
56
|
+
s.updateEnabledModels,
|
|
58
57
|
]);
|
|
59
58
|
|
|
60
59
|
const chatModelCards = useUserStore(
|
|
@@ -94,21 +93,7 @@ const ProviderModelListSelect = memo<CustomModelSelectProps>(
|
|
|
94
93
|
mode="tags"
|
|
95
94
|
notFoundContent={notFoundContent}
|
|
96
95
|
onChange={(value, options) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// if there is a new model, add it to `customModelCards`
|
|
100
|
-
options.forEach((option: { label?: string; value?: string }, index: number) => {
|
|
101
|
-
// if is a known model, it should have value
|
|
102
|
-
// if is an unknown model, the option will be {}
|
|
103
|
-
if (option.value) return;
|
|
104
|
-
|
|
105
|
-
const modelId = value[index];
|
|
106
|
-
|
|
107
|
-
dispatchCustomModelCards(provider, {
|
|
108
|
-
modelCard: { id: modelId },
|
|
109
|
-
type: 'add',
|
|
110
|
-
});
|
|
111
|
-
});
|
|
96
|
+
updateEnabledModels(provider, value, options as any[]);
|
|
112
97
|
}}
|
|
113
98
|
optionFilterProp="label"
|
|
114
99
|
optionRender={({ label, value }) => {
|
|
@@ -123,6 +108,18 @@ const ProviderModelListSelect = memo<CustomModelSelectProps>(
|
|
|
123
108
|
/>
|
|
124
109
|
);
|
|
125
110
|
|
|
111
|
+
if (enabledModels?.some((m) => value === m)) {
|
|
112
|
+
return (
|
|
113
|
+
<OptionRender
|
|
114
|
+
displayName={label as string}
|
|
115
|
+
id={value as string}
|
|
116
|
+
isAzure={showAzureDeployName}
|
|
117
|
+
provider={provider}
|
|
118
|
+
removed
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
126
123
|
// model is defined by user in client
|
|
127
124
|
return (
|
|
128
125
|
<Flexbox align={'center'} gap={8} horizontal>
|
package/src/app/layout.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { isRtlLang } from 'rtl-detect';
|
|
|
6
6
|
|
|
7
7
|
import Analytics from '@/components/Analytics';
|
|
8
8
|
import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
|
|
9
|
+
import PWAInstall from '@/features/PWAInstall';
|
|
9
10
|
import AuthProvider from '@/layout/AuthProvider';
|
|
10
11
|
import GlobalProvider from '@/layout/GlobalProvider';
|
|
11
12
|
import { isMobileDevice } from '@/utils/responsive';
|
|
@@ -31,6 +32,7 @@ const RootLayout = async ({ children, modal }: RootLayoutProps) => {
|
|
|
31
32
|
{children}
|
|
32
33
|
{modal}
|
|
33
34
|
</AuthProvider>
|
|
35
|
+
<PWAInstall />
|
|
34
36
|
</GlobalProvider>
|
|
35
37
|
<Analytics />
|
|
36
38
|
{inVercel && <SpeedInsights />}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
DeepSeek,
|
|
6
6
|
Google,
|
|
7
7
|
Groq,
|
|
8
|
+
LobeHub,
|
|
8
9
|
Minimax,
|
|
9
10
|
Mistral,
|
|
10
11
|
Moonshot,
|
|
@@ -16,7 +17,6 @@ import {
|
|
|
16
17
|
ZeroOne,
|
|
17
18
|
Zhipu,
|
|
18
19
|
} from '@lobehub/icons';
|
|
19
|
-
import { Logo } from '@lobehub/ui';
|
|
20
20
|
import { memo } from 'react';
|
|
21
21
|
import { Center } from 'react-layout-kit';
|
|
22
22
|
|
|
@@ -29,7 +29,7 @@ interface ModelProviderIconProps {
|
|
|
29
29
|
const ModelProviderIcon = memo<ModelProviderIconProps>(({ provider }) => {
|
|
30
30
|
switch (provider) {
|
|
31
31
|
case 'lobehub': {
|
|
32
|
-
return <
|
|
32
|
+
return <LobeHub size={20} />;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
case ModelProvider.ZhiPu: {
|
|
@@ -79,10 +79,10 @@ export const ModelInfoTags = memo<ModelInfoTagsProps>(
|
|
|
79
79
|
return (
|
|
80
80
|
<Flexbox direction={directionReverse ? 'horizontal-reverse' : 'horizontal'} gap={4}>
|
|
81
81
|
{model.files && (
|
|
82
|
-
<Tooltip
|
|
82
|
+
<Tooltip
|
|
83
83
|
overlayStyle={{ pointerEvents: 'none' }}
|
|
84
|
-
placement={placement}
|
|
85
|
-
title={t('ModelSelect.featureTag.file')}
|
|
84
|
+
placement={placement}
|
|
85
|
+
title={t('ModelSelect.featureTag.file')}
|
|
86
86
|
>
|
|
87
87
|
<div className={cx(styles.tag, styles.tagGreen)} style={{ cursor: 'pointer' }} title="">
|
|
88
88
|
<Icon icon={LucidePaperclip} />
|
|
@@ -90,9 +90,9 @@ export const ModelInfoTags = memo<ModelInfoTagsProps>(
|
|
|
90
90
|
</Tooltip>
|
|
91
91
|
)}
|
|
92
92
|
{model.vision && (
|
|
93
|
-
<Tooltip
|
|
93
|
+
<Tooltip
|
|
94
94
|
overlayStyle={{ pointerEvents: 'none' }}
|
|
95
|
-
placement={placement}
|
|
95
|
+
placement={placement}
|
|
96
96
|
title={t('ModelSelect.featureTag.vision')}
|
|
97
97
|
>
|
|
98
98
|
<div className={cx(styles.tag, styles.tagGreen)} style={{ cursor: 'pointer' }} title="">
|
|
@@ -128,15 +128,6 @@ export const ModelInfoTags = memo<ModelInfoTagsProps>(
|
|
|
128
128
|
</Center>
|
|
129
129
|
</Tooltip>
|
|
130
130
|
)}
|
|
131
|
-
{/*{model.isCustom && (*/}
|
|
132
|
-
{/* <Tooltip*/}
|
|
133
|
-
{/* overlayStyle={{ maxWidth: 300 }}*/}
|
|
134
|
-
{/* placement={placement}*/}
|
|
135
|
-
{/* title={t('ModelSelect.featureTag.custom')}*/}
|
|
136
|
-
{/* >*/}
|
|
137
|
-
{/* <Center className={styles.custom}>DIY</Center>*/}
|
|
138
|
-
{/* </Tooltip>*/}
|
|
139
|
-
{/*)}*/}
|
|
140
131
|
</Flexbox>
|
|
141
132
|
);
|
|
142
133
|
},
|
|
@@ -18,3 +18,4 @@ export const MOBILE_HEADER_ICON_SIZE = { blockSize: 36, fontSize: 22 };
|
|
|
18
18
|
export const DESKTOP_HEADER_ICON_SIZE = { fontSize: 24 };
|
|
19
19
|
export const HEADER_ICON_SIZE = (mobile?: boolean) =>
|
|
20
20
|
mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE;
|
|
21
|
+
export const PWA_INSTALL_ID = 'pwa-install';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import dynamic from 'next/dynamic';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
import { PWA_INSTALL_ID } from '@/const/layoutTokens';
|
|
8
|
+
import { usePlatform } from '@/hooks/usePlatform';
|
|
9
|
+
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
const PWA: any = dynamic(() => import('@khmyznikov/pwa-install/dist/pwa-install.react.js'), {
|
|
12
|
+
ssr: false,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const PWAInstall = memo(() => {
|
|
16
|
+
const { t } = useTranslation('metadata');
|
|
17
|
+
const { isPWA } = usePlatform();
|
|
18
|
+
if (isPWA) return null;
|
|
19
|
+
return <PWA description={t('chat.description')} id={PWA_INSTALL_ID} />;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export default PWAInstall;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import { pwaInstallHandler } from 'pwa-install-handler';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { PWA_INSTALL_ID } from '@/const/layoutTokens';
|
|
6
|
+
|
|
7
|
+
import { usePWAInstall } from './usePWAInstall';
|
|
8
|
+
import { usePlatform } from './usePlatform';
|
|
9
|
+
|
|
10
|
+
// Mocks
|
|
11
|
+
vi.mock('./usePlatform', () => ({
|
|
12
|
+
usePlatform: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('@/utils/env', () => ({
|
|
16
|
+
isOnServerSide: false,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock('pwa-install-handler', () => ({
|
|
20
|
+
pwaInstallHandler: {
|
|
21
|
+
addListener: vi.fn(),
|
|
22
|
+
removeListener: vi.fn(),
|
|
23
|
+
getEvent: vi.fn(),
|
|
24
|
+
},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
describe('usePWAInstall', () => {
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
vi.clearAllMocks();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return canInstall as false when in PWA', () => {
|
|
33
|
+
vi.mocked(usePlatform).mockReturnValue({ isSupportInstallPWA: true, isPWA: true } as any);
|
|
34
|
+
|
|
35
|
+
const { result } = renderHook(() => usePWAInstall());
|
|
36
|
+
|
|
37
|
+
expect(result.current.canInstall).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should return canInstall based on canInstall state when support PWA', () => {
|
|
41
|
+
vi.mocked(usePlatform).mockReturnValue({ isSupportInstallPWA: true, isPWA: false } as any);
|
|
42
|
+
|
|
43
|
+
const { result, rerender } = renderHook(() => usePWAInstall());
|
|
44
|
+
|
|
45
|
+
expect(result.current.canInstall).toBe(false);
|
|
46
|
+
|
|
47
|
+
act(() => {
|
|
48
|
+
vi.mocked(pwaInstallHandler.addListener).mock.calls[0][0](true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
rerender();
|
|
52
|
+
|
|
53
|
+
expect(result.current.canInstall).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return canInstall as true when not support PWA', () => {
|
|
57
|
+
vi.mocked(usePlatform).mockReturnValue({ isSupportInstallPWA: false, isPWA: false } as any);
|
|
58
|
+
|
|
59
|
+
const { result } = renderHook(() => usePWAInstall());
|
|
60
|
+
|
|
61
|
+
expect(result.current.canInstall).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should call pwa.showDialog when install is called', () => {
|
|
65
|
+
const mockShowDialog = vi.fn();
|
|
66
|
+
document.body.innerHTML = `<div id="${PWA_INSTALL_ID}"></div>`;
|
|
67
|
+
const pwaElement: any = document.querySelector(`#${PWA_INSTALL_ID}`);
|
|
68
|
+
pwaElement.showDialog = mockShowDialog;
|
|
69
|
+
|
|
70
|
+
const { result } = renderHook(() => usePWAInstall());
|
|
71
|
+
|
|
72
|
+
act(() => {
|
|
73
|
+
result.current.install();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(mockShowDialog).toHaveBeenCalledWith(true);
|
|
77
|
+
});
|
|
78
|
+
});
|