@lobehub/chat 0.122.2 → 0.122.4
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/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/docs/Deployment/Docker-Deployment.md +118 -0
- package/docs/Deployment/Docker-Deployment.zh-CN.md +118 -0
- package/package.json +1 -4
- package/src/app/api/config/route.ts +3 -1
- package/src/app/api/openai/chat/createChatCompletion.ts +1 -1
- package/src/app/api/openai/createBizOpenAI/index.ts +1 -1
- package/src/app/api/plugin/gateway/route.ts +31 -5
- package/src/app/chat/(mobile)/features/SessionHeader.tsx +2 -1
- package/src/app/chat/features/ChatHeader/SettingButton.tsx +1 -1
- package/src/app/chat/features/ChatHeader/ShareButton/ShareModal.tsx +2 -1
- package/src/app/chat/settings/(desktop)/index.tsx +9 -5
- package/src/app/chat/settings/features/SubmitAgentButton/SubmitAgentModal.tsx +2 -1
- package/src/app/settings/(mobile)/mobile/index.tsx +2 -1
- package/src/app/settings/common/Common.tsx +14 -3
- package/src/app/settings/features/ThemeSwatches/ThemeSwatchesNeutral.tsx +2 -1
- package/src/app/settings/features/ThemeSwatches/ThemeSwatchesPrimary.tsx +2 -1
- package/src/app/settings/llm/LLM/index.tsx +17 -10
- package/src/const/settings.ts +0 -1
- package/src/database/core/db.ts +41 -2
- package/src/database/core/migrations/migrateSettingsToUser/fixtures/input.json +55 -0
- package/src/database/core/migrations/migrateSettingsToUser/fixtures/output.json +60 -0
- package/src/database/core/migrations/migrateSettingsToUser/index.test.ts +13 -0
- package/src/database/core/migrations/migrateSettingsToUser/index.ts +39 -0
- package/src/database/core/schemas.ts +10 -0
- package/src/database/models/user.ts +54 -0
- package/src/database/schemas/user.ts +57 -0
- package/src/features/AgentSetting/AgentConfig/index.tsx +2 -2
- package/src/features/AvatarWithUpload/index.tsx +6 -2
- package/src/features/ChatInput/ActionBar/ModelSwitch.tsx +2 -2
- package/src/features/Conversation/Error/ApiKeyForm.tsx +3 -3
- package/src/features/Conversation/Error/InvalidAccess.tsx +5 -1
- package/src/features/Conversation/Extras/Translate.tsx +3 -4
- package/src/layout/GlobalLayout/AppTheme.tsx +4 -3
- package/src/layout/GlobalLayout/Locale.tsx +2 -1
- package/src/layout/GlobalLayout/StoreHydration.tsx +7 -2
- package/src/services/_header.ts +3 -2
- package/src/services/chat.ts +2 -1
- package/src/services/user.ts +31 -0
- package/src/store/chat/slices/message/selectors.test.ts +7 -2
- package/src/store/chat/slices/message/selectors.ts +2 -1
- package/src/store/global/hooks/index.ts +0 -1
- package/src/store/global/initialState.ts +3 -1
- package/src/store/global/selectors.ts +1 -0
- package/src/store/global/slices/common/action.test.ts +2 -79
- package/src/store/global/slices/common/action.ts +37 -61
- package/src/store/global/slices/common/initialState.ts +0 -34
- package/src/store/global/slices/common/selectors.ts +2 -7
- package/src/store/global/slices/preference/action.test.ts +92 -0
- package/src/store/global/slices/preference/action.ts +75 -0
- package/src/store/global/slices/preference/initialState.ts +45 -0
- package/src/store/global/slices/preference/selectors.ts +10 -0
- package/src/store/global/slices/settings/action.test.ts +68 -39
- package/src/store/global/slices/settings/action.ts +26 -50
- package/src/store/global/slices/settings/initialState.ts +8 -6
- package/src/store/global/slices/settings/selectors/__snapshots__/modelProvider.test.ts.snap +100 -0
- package/src/store/global/slices/settings/{__snapshots__ → selectors/__snapshots__}/selectors.test.ts.snap +1 -115
- package/src/store/global/slices/settings/selectors/index.ts +2 -0
- package/src/store/global/slices/settings/selectors/modelProvider.test.ts +100 -0
- package/src/store/global/slices/settings/selectors/modelProvider.ts +64 -0
- package/src/store/global/slices/settings/{selectors.test.ts → selectors/selectors.test.ts} +2 -97
- package/src/store/global/slices/settings/selectors/settings.ts +58 -0
- package/src/store/global/store.ts +11 -36
- package/src/store/session/slices/agent/action.ts +1 -1
- package/src/types/settings/base.ts +13 -0
- package/src/types/settings/index.ts +34 -0
- package/src/types/settings/modelProvider.ts +17 -0
- package/src/types/settings/tts.ts +10 -0
- package/src/utils/difference.ts +12 -0
- package/tests/utils.tsx +8 -0
- package/tsconfig.json +9 -8
- package/vitest.config.ts +1 -0
- package/src/store/global/hooks/useHydrated.ts +0 -18
- package/src/store/global/slices/settings/selectors.ts +0 -105
- package/src/types/settings.ts +0 -68
- /package/src/app/api/{openai/errorResponse.test.ts → errorResponse.test.ts} +0 -0
- /package/src/app/api/{openai/errorResponse.ts → errorResponse.ts} +0 -0
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
import { memo } from 'react';
|
|
9
9
|
|
|
10
10
|
import { useGlobalStore } from '@/store/global';
|
|
11
|
+
import { settingsSelectors } from '@/store/global/selectors';
|
|
11
12
|
|
|
12
13
|
const ThemeSwatchesPrimary = memo(() => {
|
|
13
14
|
const [primaryColor, setSettings] = useGlobalStore((s) => [
|
|
14
|
-
s.
|
|
15
|
+
settingsSelectors.currentSettings(s).primaryColor,
|
|
15
16
|
s.setSettings,
|
|
16
17
|
]);
|
|
17
18
|
|
|
@@ -3,12 +3,12 @@ import { Form as AntForm, AutoComplete, Input, Switch } from 'antd';
|
|
|
3
3
|
import { createStyles } from 'antd-style';
|
|
4
4
|
import { debounce } from 'lodash-es';
|
|
5
5
|
import { Webhook } from 'lucide-react';
|
|
6
|
-
import { memo } from 'react';
|
|
6
|
+
import { memo, useEffect } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
9
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { useGlobalStore } from '@/store/global';
|
|
11
|
+
import { modelProviderSelectors } from '@/store/global/selectors';
|
|
12
12
|
|
|
13
13
|
import Checker from './Checker';
|
|
14
14
|
|
|
@@ -35,16 +35,23 @@ const LLM = memo(() => {
|
|
|
35
35
|
const { t } = useTranslation('setting');
|
|
36
36
|
const [form] = AntForm.useForm();
|
|
37
37
|
const { styles } = useStyles();
|
|
38
|
-
const [setSettings] = useGlobalStore((s) => [
|
|
38
|
+
const [useAzure, setSettings] = useGlobalStore((s) => [
|
|
39
|
+
modelProviderSelectors.enableAzure(s),
|
|
40
|
+
s.setSettings,
|
|
41
|
+
]);
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const unsubscribe = useGlobalStore.subscribe(
|
|
45
|
+
(s) => s.settings,
|
|
46
|
+
(settings) => {
|
|
47
|
+
form.setFieldsValue(settings);
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
return () => {
|
|
51
|
+
unsubscribe();
|
|
52
|
+
};
|
|
44
53
|
}, []);
|
|
45
54
|
|
|
46
|
-
const useAzure = useGlobalStore((s) => s.settings.languageModel.openAI.useAzure);
|
|
47
|
-
|
|
48
55
|
const openAI: ItemGroup = {
|
|
49
56
|
children: [
|
|
50
57
|
{
|
package/src/const/settings.ts
CHANGED
package/src/database/core/db.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import Dexie, { Transaction } from 'dexie';
|
|
2
2
|
|
|
3
|
-
import { DBModel, LOBE_CHAT_LOCAL_DB_NAME } from '@/database/core/types/db';
|
|
4
3
|
import { DB_File } from '@/database/schemas/files';
|
|
5
4
|
import { DB_Message } from '@/database/schemas/message';
|
|
6
5
|
import { DB_Plugin } from '@/database/schemas/plugin';
|
|
7
6
|
import { DB_Session } from '@/database/schemas/session';
|
|
8
7
|
import { DB_SessionGroup } from '@/database/schemas/sessionGroup';
|
|
9
8
|
import { DB_Topic } from '@/database/schemas/topic';
|
|
9
|
+
import { DB_User } from '@/database/schemas/user';
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { migrateSettingsToUser } from './migrations/migrateSettingsToUser';
|
|
12
|
+
import { dbSchemaV1, dbSchemaV2, dbSchemaV3, dbSchemaV4, dbSchemaV5 } from './schemas';
|
|
13
|
+
import { DBModel, LOBE_CHAT_LOCAL_DB_NAME } from './types/db';
|
|
12
14
|
|
|
13
15
|
interface LobeDBSchemaMap {
|
|
14
16
|
files: DB_File;
|
|
@@ -17,6 +19,7 @@ interface LobeDBSchemaMap {
|
|
|
17
19
|
sessionGroups: DB_SessionGroup;
|
|
18
20
|
sessions: DB_Session;
|
|
19
21
|
topics: DB_Topic;
|
|
22
|
+
users: DB_User;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
// Define a local DB
|
|
@@ -27,6 +30,7 @@ export class LocalDB extends Dexie {
|
|
|
27
30
|
public topics: LobeDBTable<'topics'>;
|
|
28
31
|
public plugins: LobeDBTable<'plugins'>;
|
|
29
32
|
public sessionGroups: LobeDBTable<'sessionGroups'>;
|
|
33
|
+
public users: LobeDBTable<'users'>;
|
|
30
34
|
|
|
31
35
|
constructor() {
|
|
32
36
|
super(LOBE_CHAT_LOCAL_DB_NAME);
|
|
@@ -37,12 +41,17 @@ export class LocalDB extends Dexie {
|
|
|
37
41
|
.stores(dbSchemaV4)
|
|
38
42
|
.upgrade((trans) => this.upgradeToV4(trans));
|
|
39
43
|
|
|
44
|
+
this.version(5)
|
|
45
|
+
.stores(dbSchemaV5)
|
|
46
|
+
.upgrade((trans) => this.upgradeToV5(trans));
|
|
47
|
+
|
|
40
48
|
this.files = this.table('files');
|
|
41
49
|
this.sessions = this.table('sessions');
|
|
42
50
|
this.messages = this.table('messages');
|
|
43
51
|
this.topics = this.table('topics');
|
|
44
52
|
this.plugins = this.table('plugins');
|
|
45
53
|
this.sessionGroups = this.table('sessionGroups');
|
|
54
|
+
this.users = this.table('users');
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
/**
|
|
@@ -59,6 +68,36 @@ export class LocalDB extends Dexie {
|
|
|
59
68
|
session.group = 'default';
|
|
60
69
|
});
|
|
61
70
|
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 2024.01.29
|
|
74
|
+
* settings from localStorage to indexedDB
|
|
75
|
+
*/
|
|
76
|
+
upgradeToV5 = async (trans: Transaction) => {
|
|
77
|
+
const users = trans.table('users');
|
|
78
|
+
|
|
79
|
+
// if no user, create one
|
|
80
|
+
if ((await users.count()) === 0) {
|
|
81
|
+
const data = localStorage.getItem('LOBE_SETTINGS');
|
|
82
|
+
|
|
83
|
+
if (data) {
|
|
84
|
+
let json;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
json = JSON.parse(data);
|
|
88
|
+
} catch {
|
|
89
|
+
/* empty */
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!json?.state?.settings) return;
|
|
93
|
+
|
|
94
|
+
const settings = json.state.settings;
|
|
95
|
+
|
|
96
|
+
const user = migrateSettingsToUser(settings);
|
|
97
|
+
await users.add(user);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
62
101
|
}
|
|
63
102
|
|
|
64
103
|
export const LocalDBInstance = new LocalDB();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"avatar": "",
|
|
3
|
+
"defaultAgent": {
|
|
4
|
+
"config": {
|
|
5
|
+
"autoCreateTopicThreshold": 2,
|
|
6
|
+
"displayMode": "chat",
|
|
7
|
+
"enableAutoCreateTopic": true,
|
|
8
|
+
"historyCount": 1,
|
|
9
|
+
"model": "gpt-3.5-turbo-1106",
|
|
10
|
+
"params": {
|
|
11
|
+
"frequency_penalty": 0,
|
|
12
|
+
"presence_penalty": 0,
|
|
13
|
+
"temperature": 0.7,
|
|
14
|
+
"top_p": 0.7
|
|
15
|
+
},
|
|
16
|
+
"plugins": ["website-crawler"],
|
|
17
|
+
"systemRole": "",
|
|
18
|
+
"tts": {
|
|
19
|
+
"showAllLocaleVoice": false,
|
|
20
|
+
"sttLocale": "auto",
|
|
21
|
+
"ttsService": "openai",
|
|
22
|
+
"voice": { "openai": "alloy" }
|
|
23
|
+
},
|
|
24
|
+
"enableHistoryCount": true
|
|
25
|
+
},
|
|
26
|
+
"meta": {}
|
|
27
|
+
},
|
|
28
|
+
"fontSize": 14,
|
|
29
|
+
"language": "auto",
|
|
30
|
+
"languageModel": {
|
|
31
|
+
"openAI": {
|
|
32
|
+
"OPENAI_API_KEY": "dfdf",
|
|
33
|
+
"models": [
|
|
34
|
+
"gpt-3.5-turbo",
|
|
35
|
+
"gpt-3.5-turbo-1106",
|
|
36
|
+
"gpt-3.5-turbo-16k",
|
|
37
|
+
"gpt-4",
|
|
38
|
+
"gpt-4-32k",
|
|
39
|
+
"gpt-4-1106-preview",
|
|
40
|
+
"gpt-4-vision-preview"
|
|
41
|
+
],
|
|
42
|
+
"customModelName": "+glm-4,+glm-4v",
|
|
43
|
+
"endpoint": "sdaddsd"
|
|
44
|
+
},
|
|
45
|
+
"zhipu": { "ZHIPU_API_KEY": "", "enabled": false }
|
|
46
|
+
},
|
|
47
|
+
"password": "",
|
|
48
|
+
"themeMode": "auto",
|
|
49
|
+
"tool": { "dalle": { "autoGenerate": false } },
|
|
50
|
+
"tts": {
|
|
51
|
+
"openAI": { "sttModel": "whisper-1", "ttsModel": "tts-1" },
|
|
52
|
+
"sttAutoStop": true,
|
|
53
|
+
"sttServer": "openai"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"avatar": "",
|
|
3
|
+
"settings": {
|
|
4
|
+
"defaultAgent": {
|
|
5
|
+
"config": {
|
|
6
|
+
"autoCreateTopicThreshold": 2,
|
|
7
|
+
"displayMode": "chat",
|
|
8
|
+
"enableAutoCreateTopic": true,
|
|
9
|
+
"historyCount": 1,
|
|
10
|
+
"model": "gpt-3.5-turbo-1106",
|
|
11
|
+
"params": {
|
|
12
|
+
"frequency_penalty": 0,
|
|
13
|
+
"presence_penalty": 0,
|
|
14
|
+
"temperature": 0.7,
|
|
15
|
+
"top_p": 0.7
|
|
16
|
+
},
|
|
17
|
+
"plugins": ["website-crawler"],
|
|
18
|
+
"systemRole": "",
|
|
19
|
+
"tts": {
|
|
20
|
+
"showAllLocaleVoice": false,
|
|
21
|
+
"sttLocale": "auto",
|
|
22
|
+
"ttsService": "openai",
|
|
23
|
+
"voice": {
|
|
24
|
+
"openai": "alloy"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"enableHistoryCount": true
|
|
28
|
+
},
|
|
29
|
+
"meta": {}
|
|
30
|
+
},
|
|
31
|
+
"fontSize": 14,
|
|
32
|
+
"language": "auto",
|
|
33
|
+
"languageModel": {
|
|
34
|
+
"openai": {
|
|
35
|
+
"OPENAI_API_KEY": "dfdf",
|
|
36
|
+
"models": [
|
|
37
|
+
"gpt-3.5-turbo",
|
|
38
|
+
"gpt-3.5-turbo-1106",
|
|
39
|
+
"gpt-3.5-turbo-16k",
|
|
40
|
+
"gpt-4",
|
|
41
|
+
"gpt-4-32k",
|
|
42
|
+
"gpt-4-1106-preview",
|
|
43
|
+
"gpt-4-vision-preview"
|
|
44
|
+
],
|
|
45
|
+
"customModelName": "+glm-4,+glm-4v",
|
|
46
|
+
"endpoint": "sdaddsd"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"password": "",
|
|
50
|
+
"themeMode": "auto",
|
|
51
|
+
"tts": {
|
|
52
|
+
"openAI": {
|
|
53
|
+
"sttModel": "whisper-1",
|
|
54
|
+
"ttsModel": "tts-1"
|
|
55
|
+
},
|
|
56
|
+
"sttAutoStop": true,
|
|
57
|
+
"sttServer": "openai"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import input from './fixtures/input.json';
|
|
4
|
+
import outputData from './fixtures/output.json';
|
|
5
|
+
import { V4Settings, migrateSettingsToUser } from './index';
|
|
6
|
+
|
|
7
|
+
describe('migrateSettingsFromLocalStorage', () => {
|
|
8
|
+
it('from localStorage to indexedDB', () => {
|
|
9
|
+
const output = migrateSettingsToUser(input as V4Settings);
|
|
10
|
+
|
|
11
|
+
expect(output).toEqual(outputData);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NeutralColors, PrimaryColors } from '@lobehub/ui';
|
|
2
|
+
import type { ThemeMode } from 'antd-style';
|
|
3
|
+
|
|
4
|
+
import { DB_Settings, DB_User } from '@/database/schemas/user';
|
|
5
|
+
import { LocaleMode } from '@/types/locale';
|
|
6
|
+
import { GlobalDefaultAgent, GlobalLLMConfig, GlobalTTSConfig, GlobalTool } from '@/types/settings';
|
|
7
|
+
|
|
8
|
+
export interface V4Settings {
|
|
9
|
+
avatar: string;
|
|
10
|
+
defaultAgent: GlobalDefaultAgent;
|
|
11
|
+
fontSize: number;
|
|
12
|
+
language: LocaleMode;
|
|
13
|
+
languageModel: GlobalLLMConfig;
|
|
14
|
+
neutralColor?: NeutralColors;
|
|
15
|
+
password: string;
|
|
16
|
+
primaryColor?: PrimaryColors;
|
|
17
|
+
themeMode: ThemeMode;
|
|
18
|
+
tool: GlobalTool;
|
|
19
|
+
tts: GlobalTTSConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const migrateSettingsToUser = (settings: V4Settings): DB_User => {
|
|
23
|
+
const dbSettings: DB_Settings = {
|
|
24
|
+
defaultAgent: settings.defaultAgent,
|
|
25
|
+
fontSize: settings.fontSize,
|
|
26
|
+
language: settings.language,
|
|
27
|
+
languageModel: {
|
|
28
|
+
openai: settings.languageModel.openAI,
|
|
29
|
+
},
|
|
30
|
+
password: settings.password,
|
|
31
|
+
themeMode: settings.themeMode,
|
|
32
|
+
tts: settings.tts,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
avatar: settings.avatar,
|
|
37
|
+
settings: dbSettings,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -44,3 +44,13 @@ export const dbSchemaV4 = {
|
|
|
44
44
|
sessions:
|
|
45
45
|
'&id, type, group, pinned, meta.title, meta.description, meta.tags, createdAt, updatedAt',
|
|
46
46
|
};
|
|
47
|
+
|
|
48
|
+
// ************************************** //
|
|
49
|
+
// ******* Version 5 - 2024-01-29 ******* //
|
|
50
|
+
// ************************************** //
|
|
51
|
+
// - Added `users` table
|
|
52
|
+
|
|
53
|
+
export const dbSchemaV5 = {
|
|
54
|
+
...dbSchemaV4,
|
|
55
|
+
users: '++id',
|
|
56
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { DeepPartial } from 'utility-types';
|
|
2
|
+
|
|
3
|
+
import { BaseModel } from '@/database/core';
|
|
4
|
+
import { GlobalSettings } from '@/types/settings';
|
|
5
|
+
|
|
6
|
+
import { DB_User, DB_UserSchema } from '../schemas/user';
|
|
7
|
+
|
|
8
|
+
class _UserModel extends BaseModel {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('users', DB_UserSchema);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
getUser = async (): Promise<DB_User & { id: number }> => {
|
|
14
|
+
const hasUSer = !!(await this.table.count());
|
|
15
|
+
|
|
16
|
+
if (!hasUSer) await this.table.put({});
|
|
17
|
+
|
|
18
|
+
const list = await this.table.toArray();
|
|
19
|
+
|
|
20
|
+
return list[0];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
create = async (user: DB_User) => {
|
|
24
|
+
return this.table.put(user);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
private update = async (id: number, value: DeepPartial<DB_User>) => {
|
|
28
|
+
return this.table.update(id, value);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
clear() {
|
|
32
|
+
return this.table.clear();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async updateSettings(settings: DeepPartial<GlobalSettings>) {
|
|
36
|
+
const user = await this.getUser();
|
|
37
|
+
|
|
38
|
+
return this.update(user.id, { settings: settings as any });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async resetSettings() {
|
|
42
|
+
const user = await this.getUser();
|
|
43
|
+
|
|
44
|
+
return this.update(user.id, { avatar: undefined, settings: undefined });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async updateAvatar(avatar: string) {
|
|
48
|
+
const user = await this.getUser();
|
|
49
|
+
|
|
50
|
+
return this.update(user.id, { avatar });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const UserModel = new _UserModel();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { AgentSchema } from '@/database/schemas/session';
|
|
4
|
+
import { LobeMetaDataSchema } from '@/types/meta';
|
|
5
|
+
|
|
6
|
+
const modelProviderSchema = z.object({
|
|
7
|
+
openai: z.object({
|
|
8
|
+
OPENAI_API_KEY: z.string().optional(),
|
|
9
|
+
azureApiVersion: z.string().optional(),
|
|
10
|
+
customModelName: z.string().optional(),
|
|
11
|
+
endpoint: z.string().optional(),
|
|
12
|
+
models: z.array(z.string()).optional(),
|
|
13
|
+
useAzure: z.boolean().optional(),
|
|
14
|
+
}),
|
|
15
|
+
// zhipu: z.object({
|
|
16
|
+
// ZHIPU_API_KEY: z.string().optional(),
|
|
17
|
+
// enabled: z.boolean().default(false),
|
|
18
|
+
// }),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const settingsSchema = z.object({
|
|
22
|
+
defaultAgent: z.object({
|
|
23
|
+
config: AgentSchema,
|
|
24
|
+
meta: LobeMetaDataSchema,
|
|
25
|
+
}),
|
|
26
|
+
fontSize: z.number().default(14),
|
|
27
|
+
language: z.string(),
|
|
28
|
+
languageModel: modelProviderSchema.partial(),
|
|
29
|
+
password: z.string(),
|
|
30
|
+
themeMode: z.string(),
|
|
31
|
+
tts: z.object({
|
|
32
|
+
openAI: z.object({
|
|
33
|
+
sttModel: z.string(),
|
|
34
|
+
ttsModel: z.string(),
|
|
35
|
+
}),
|
|
36
|
+
sttAutoStop: z.boolean(),
|
|
37
|
+
sttServer: z.string(),
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// const patchSchema = z.array(
|
|
42
|
+
// z.object({
|
|
43
|
+
// op: z.string(),
|
|
44
|
+
// path: z.string(),
|
|
45
|
+
// value: z.any(),
|
|
46
|
+
// }),
|
|
47
|
+
// );
|
|
48
|
+
|
|
49
|
+
export const DB_UserSchema = z.object({
|
|
50
|
+
avatar: z.string().optional(),
|
|
51
|
+
settings: settingsSchema.partial(),
|
|
52
|
+
// settings: patchSchema,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export type DB_User = z.infer<typeof DB_UserSchema>;
|
|
56
|
+
|
|
57
|
+
export type DB_Settings = z.infer<typeof settingsSchema>;
|
|
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
9
9
|
|
|
10
10
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
11
11
|
import { useGlobalStore } from '@/store/global';
|
|
12
|
-
import {
|
|
12
|
+
import { modelProviderSelectors } from '@/store/global/selectors';
|
|
13
13
|
|
|
14
14
|
import { useStore } from '../store';
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ const AgentConfig = memo(() => {
|
|
|
21
21
|
|
|
22
22
|
const config = useStore((s) => s.config, isEqual);
|
|
23
23
|
|
|
24
|
-
const modelList = useGlobalStore(
|
|
24
|
+
const modelList = useGlobalStore(modelProviderSelectors.modelList);
|
|
25
25
|
|
|
26
26
|
useEffect(() => {
|
|
27
27
|
form.setFieldsValue(config);
|
|
@@ -5,6 +5,7 @@ import Avatar from 'next/image';
|
|
|
5
5
|
import { CSSProperties, memo } from 'react';
|
|
6
6
|
|
|
7
7
|
import { useGlobalStore } from '@/store/global';
|
|
8
|
+
import { commonSelectors } from '@/store/global/selectors';
|
|
8
9
|
import { imageToBase64 } from '@/utils/imageToBase64';
|
|
9
10
|
import { createUploadImageHandler } from '@/utils/uploadFIle';
|
|
10
11
|
|
|
@@ -36,15 +37,18 @@ interface AvatarWithUploadProps {
|
|
|
36
37
|
|
|
37
38
|
const AvatarWithUpload = memo<AvatarWithUploadProps>(
|
|
38
39
|
({ size = 40, compressSize = 256, style, id }) => {
|
|
39
|
-
const [avatar, setSettings] = useGlobalStore((st) => [st.settings.avatar, st.setSettings]);
|
|
40
40
|
const { styles } = useStyle();
|
|
41
|
+
const [avatar, updateAvatar] = useGlobalStore((s) => [
|
|
42
|
+
commonSelectors.userAvatar(s),
|
|
43
|
+
s.updateAvatar,
|
|
44
|
+
]);
|
|
41
45
|
|
|
42
46
|
const handleUploadAvatar = createUploadImageHandler((avatar) => {
|
|
43
47
|
const img = new Image();
|
|
44
48
|
img.src = avatar;
|
|
45
49
|
img.addEventListener('load', () => {
|
|
46
50
|
const webpBase64 = imageToBase64({ img, size: compressSize });
|
|
47
|
-
|
|
51
|
+
updateAvatar(webpBase64);
|
|
48
52
|
});
|
|
49
53
|
});
|
|
50
54
|
|
|
@@ -6,7 +6,7 @@ import { memo } from 'react';
|
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
8
8
|
import { useGlobalStore } from '@/store/global';
|
|
9
|
-
import {
|
|
9
|
+
import { modelProviderSelectors } from '@/store/global/selectors';
|
|
10
10
|
import { useSessionStore } from '@/store/session';
|
|
11
11
|
import { agentSelectors } from '@/store/session/selectors';
|
|
12
12
|
import { LanguageModel } from '@/types/llm';
|
|
@@ -18,7 +18,7 @@ const ModelSwitch = memo(() => {
|
|
|
18
18
|
return [agentSelectors.currentAgentModel(s), s.updateAgentConfig];
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
const modelList = useGlobalStore(
|
|
21
|
+
const modelList = useGlobalStore(modelProviderSelectors.modelList, isEqual);
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<Dropdown
|
|
@@ -7,7 +7,7 @@ import { Center, Flexbox } from 'react-layout-kit';
|
|
|
7
7
|
|
|
8
8
|
import { useChatStore } from '@/store/chat';
|
|
9
9
|
import { useGlobalStore } from '@/store/global';
|
|
10
|
-
import {
|
|
10
|
+
import { modelProviderSelectors } from '@/store/global/selectors';
|
|
11
11
|
|
|
12
12
|
import { FormAction } from './style';
|
|
13
13
|
|
|
@@ -16,8 +16,8 @@ const APIKeyForm = memo<{ id: string }>(({ id }) => {
|
|
|
16
16
|
const [showProxy, setShow] = useState(false);
|
|
17
17
|
|
|
18
18
|
const [apiKey, proxyUrl, setConfig] = useGlobalStore((s) => [
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
modelProviderSelectors.openAIAPI(s),
|
|
20
|
+
modelProviderSelectors.openAIProxyUrl(s),
|
|
21
21
|
s.setOpenAIConfig,
|
|
22
22
|
]);
|
|
23
23
|
|
|
@@ -7,6 +7,7 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
7
7
|
|
|
8
8
|
import { useChatStore } from '@/store/chat';
|
|
9
9
|
import { useGlobalStore } from '@/store/global';
|
|
10
|
+
import { settingsSelectors } from '@/store/global/selectors';
|
|
10
11
|
|
|
11
12
|
import { RenderErrorMessage } from '../types';
|
|
12
13
|
import APIKeyForm from './ApiKeyForm';
|
|
@@ -20,7 +21,10 @@ enum Tab {
|
|
|
20
21
|
const InvalidAccess: RenderErrorMessage['Render'] = memo(({ id }) => {
|
|
21
22
|
const { t } = useTranslation('error');
|
|
22
23
|
const [mode, setMode] = useState<Tab>(Tab.Password);
|
|
23
|
-
const [password, setSettings] = useGlobalStore((s) => [
|
|
24
|
+
const [password, setSettings] = useGlobalStore((s) => [
|
|
25
|
+
settingsSelectors.currentSettings(s).password,
|
|
26
|
+
s.setSettings,
|
|
27
|
+
]);
|
|
24
28
|
const [resend, deleteMessage] = useChatStore((s) => [s.resendMessage, s.deleteMessage]);
|
|
25
29
|
|
|
26
30
|
return (
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { ActionIcon, Icon, Markdown, Tag } from '@lobehub/ui';
|
|
1
|
+
import { ActionIcon, Icon, Markdown, Tag, copyToClipboard } from '@lobehub/ui';
|
|
2
2
|
import { App } from 'antd';
|
|
3
3
|
import { createStyles } from 'antd-style';
|
|
4
|
-
import copy from 'copy-to-clipboard';
|
|
5
4
|
import { ChevronDown, ChevronUp, ChevronsRight, CopyIcon, TrashIcon } from 'lucide-react';
|
|
6
5
|
import { memo, useState } from 'react';
|
|
7
6
|
import { useTranslation } from 'react-i18next';
|
|
@@ -41,8 +40,8 @@ const Translate = memo<TranslateProps>(({ content = '', from, to, id, loading })
|
|
|
41
40
|
<Flexbox horizontal>
|
|
42
41
|
<ActionIcon
|
|
43
42
|
icon={CopyIcon}
|
|
44
|
-
onClick={() => {
|
|
45
|
-
|
|
43
|
+
onClick={async () => {
|
|
44
|
+
await copyToClipboard(content);
|
|
46
45
|
message.success(t('copySuccess'));
|
|
47
46
|
}}
|
|
48
47
|
size={'small'}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
LOBE_THEME_PRIMARY_COLOR,
|
|
10
10
|
} from '@/const/theme';
|
|
11
11
|
import { useGlobalStore } from '@/store/global';
|
|
12
|
+
import { settingsSelectors } from '@/store/global/selectors';
|
|
12
13
|
import { GlobalStyle } from '@/styles';
|
|
13
14
|
import { setCookie } from '@/utils/cookie';
|
|
14
15
|
|
|
@@ -24,11 +25,11 @@ const AppTheme = memo<AppThemeProps>(
|
|
|
24
25
|
// console.debug('server:appearance', defaultAppearance);
|
|
25
26
|
// console.debug('server:primaryColor', defaultPrimaryColor);
|
|
26
27
|
// console.debug('server:neutralColor', defaultNeutralColor);
|
|
27
|
-
const themeMode = useGlobalStore((s) => s.
|
|
28
|
+
const themeMode = useGlobalStore((s) => settingsSelectors.currentSettings(s).themeMode);
|
|
28
29
|
|
|
29
30
|
const [primaryColor, neutralColor] = useGlobalStore((s) => [
|
|
30
|
-
s.
|
|
31
|
-
s.
|
|
31
|
+
settingsSelectors.currentSettings(s).primaryColor,
|
|
32
|
+
settingsSelectors.currentSettings(s).neutralColor,
|
|
32
33
|
]);
|
|
33
34
|
|
|
34
35
|
useEffect(() => {
|
|
@@ -5,6 +5,7 @@ import useSWR from 'swr';
|
|
|
5
5
|
import { createI18nNext } from '@/locales/create';
|
|
6
6
|
import { normalizeLocale } from '@/locales/resources';
|
|
7
7
|
import { useOnFinishHydrationGlobal } from '@/store/global';
|
|
8
|
+
import { settingsSelectors } from '@/store/global/selectors';
|
|
8
9
|
import { isOnServerSide } from '@/utils/env';
|
|
9
10
|
import { switchLang } from '@/utils/switchLang';
|
|
10
11
|
|
|
@@ -48,7 +49,7 @@ const Locale = memo<LocaleLayoutProps>(({ children, defaultLang }) => {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
useOnFinishHydrationGlobal((s) => {
|
|
51
|
-
if (s.
|
|
52
|
+
if (settingsSelectors.currentSettings(s).language === 'auto') {
|
|
52
53
|
switchLang('auto');
|
|
53
54
|
}
|
|
54
55
|
}, []);
|
|
@@ -5,8 +5,13 @@ import { memo, useEffect } from 'react';
|
|
|
5
5
|
import { useEffectAfterGlobalHydrated, useGlobalStore } from '@/store/global';
|
|
6
6
|
|
|
7
7
|
const StoreHydration = memo(() => {
|
|
8
|
-
const
|
|
9
|
-
|
|
8
|
+
const [useFetchServerConfig, useFetchUserConfig] = useGlobalStore((s) => [
|
|
9
|
+
s.useFetchServerConfig,
|
|
10
|
+
s.useFetchUserConfig,
|
|
11
|
+
]);
|
|
12
|
+
const { isLoading } = useFetchServerConfig();
|
|
13
|
+
|
|
14
|
+
useFetchUserConfig(!isLoading);
|
|
10
15
|
|
|
11
16
|
useEffect(() => {
|
|
12
17
|
// refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
|
package/src/services/_header.ts
CHANGED
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
USE_AZURE_OPENAI,
|
|
7
7
|
} from '@/const/fetch';
|
|
8
8
|
import { useGlobalStore } from '@/store/global';
|
|
9
|
+
import { modelProviderSelectors, settingsSelectors } from '@/store/global/selectors';
|
|
9
10
|
|
|
10
11
|
// eslint-disable-next-line no-undef
|
|
11
12
|
export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {
|
|
12
|
-
const openai = useGlobalStore.getState()
|
|
13
|
+
const openai = modelProviderSelectors.openAIConfig(useGlobalStore.getState());
|
|
13
14
|
|
|
14
15
|
const apiKey = openai.OPENAI_API_KEY || '';
|
|
15
16
|
const endpoint = openai.endpoint || '';
|
|
@@ -17,7 +18,7 @@ export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {
|
|
|
17
18
|
// eslint-disable-next-line no-undef
|
|
18
19
|
const result: HeadersInit = {
|
|
19
20
|
...header,
|
|
20
|
-
[LOBE_CHAT_ACCESS_CODE]: useGlobalStore.getState()
|
|
21
|
+
[LOBE_CHAT_ACCESS_CODE]: settingsSelectors.password(useGlobalStore.getState()),
|
|
21
22
|
[OPENAI_API_KEY_HEADER_KEY]: apiKey,
|
|
22
23
|
[OPENAI_END_POINT]: endpoint,
|
|
23
24
|
};
|