@lobehub/chat 0.72.4 → 0.73.0

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/next.config.mjs +6 -2
  3. package/package.json +5 -10
  4. package/src/config/server.ts +2 -0
  5. package/src/const/fetch.ts +15 -2
  6. package/src/const/llm.ts +17 -0
  7. package/src/const/settings.ts +16 -8
  8. package/src/locales/default/setting.ts +59 -13
  9. package/src/pages/api/{openai.ts → createChatCompletion.ts} +7 -36
  10. package/src/pages/api/openai/chat.api.ts +43 -0
  11. package/src/pages/api/openai/createAzureOpenai.ts +26 -0
  12. package/src/pages/api/openai/createOpenai.ts +17 -0
  13. package/src/pages/api/openai/models.api.ts +17 -0
  14. package/src/pages/settings/features/Settings/Common.tsx +2 -26
  15. package/src/pages/settings/features/Settings/LLM/Checker.tsx +85 -0
  16. package/src/pages/settings/features/Settings/LLM/ModelList.tsx +51 -0
  17. package/src/pages/settings/features/Settings/LLM/getModelList.ts +9 -0
  18. package/src/pages/settings/features/Settings/LLM/index.tsx +152 -0
  19. package/src/pages/settings/features/Settings/index.tsx +13 -12
  20. package/src/services/_header.ts +33 -0
  21. package/src/services/_url.ts +17 -0
  22. package/src/services/chatModel.ts +4 -10
  23. package/src/services/modelList.ts +15 -0
  24. package/src/services/plugin.ts +1 -1
  25. package/src/services/pluginMarket.ts +1 -1
  26. package/src/store/global/hooks/index.ts +1 -0
  27. package/src/store/global/hooks/useEffectAfterHydrated.ts +22 -0
  28. package/src/store/global/initialState.ts +4 -0
  29. package/src/store/global/selectors.test.ts +264 -0
  30. package/src/store/global/selectors.ts +4 -5
  31. package/src/store/global/slices/settings.ts +16 -4
  32. package/src/store/global/store.ts +34 -5
  33. package/src/types/settings.ts +45 -8
  34. package/src/utils/fetch.ts +4 -12
  35. package/src/pages/api/openai.api.ts +0 -24
  36. package/src/services/url.ts +0 -8
@@ -1,10 +1,13 @@
1
1
  import { ThemeMode } from 'antd-style';
2
+ import isEqual from 'fast-deep-equal';
2
3
  import { produce } from 'immer';
3
- import { merge } from 'lodash-es';
4
+ import { DeepPartial } from 'utility-types';
4
5
  import type { StateCreator } from 'zustand/vanilla';
5
6
 
6
7
  import { DEFAULT_AGENT, DEFAULT_SETTINGS } from '@/const/settings';
8
+ import { SettingsTabs } from '@/store/global/initialState';
7
9
  import type { GlobalSettings } from '@/types/settings';
10
+ import { merge } from '@/utils/merge';
8
11
  import { setNamespace } from '@/utils/storeDebug';
9
12
 
10
13
  import type { GlobalStore } from '../store';
@@ -24,7 +27,8 @@ export interface SettingsAction {
24
27
  * 设置部分配置设置
25
28
  * @param settings - 部分配置设置
26
29
  */
27
- setSettings: (settings: Partial<GlobalSettings>) => void;
30
+ setSettings: (settings: DeepPartial<GlobalSettings>) => void;
31
+ switchSettingTabs: (tab: SettingsTabs) => void;
28
32
  /**
29
33
  * 设置主题模式
30
34
  * @param themeMode - 主题模式
@@ -58,11 +62,19 @@ export const createSettingsSlice: StateCreator<
58
62
  resetSettings: () => {
59
63
  set({ settings: DEFAULT_SETTINGS }, false, t('resetSettings'));
60
64
  },
61
-
62
65
  setSettings: (settings) => {
63
66
  const oldSetting = get().settings;
64
- set({ settings: merge({}, oldSetting, settings) }, false, t('setSettings', settings));
67
+ const nextSettings = merge(oldSetting, settings);
68
+
69
+ if (isEqual(oldSetting, nextSettings)) return;
70
+
71
+ set({ settings: merge(oldSetting, settings) }, false, t('setSettings', settings));
65
72
  },
73
+
74
+ switchSettingTabs: (tab) => {
75
+ set({ settingsTab: tab });
76
+ },
77
+
66
78
  switchThemeMode: (themeMode) => {
67
79
  get().setSettings({ themeMode });
68
80
  },
@@ -4,9 +4,10 @@ import { shallow } from 'zustand/shallow';
4
4
  import { createWithEqualityFn } from 'zustand/traditional';
5
5
  import { StateCreator } from 'zustand/vanilla';
6
6
 
7
- import { DEFAULT_AGENT } from '@/const/settings';
7
+ import { DEFAULT_AGENT, DEFAULT_LLM_CONFIG } from '@/const/settings';
8
8
  import { isDev } from '@/utils/env';
9
9
 
10
+ import { createHyperStorage } from '../middleware/createHyperStorage';
10
11
  import { type GlobalState, initialState } from './initialState';
11
12
  import { type AgentAction, createAgentSlice } from './slices/agent';
12
13
  import { type CommonAction, createCommonSlice } from './slices/common';
@@ -29,6 +30,7 @@ type GlobalPersist = Pick<GlobalStore, 'preference' | 'settings'>;
29
30
  const persistOptions: PersistOptions<GlobalStore, GlobalPersist> = {
30
31
  merge: (persistedState, currentState) => {
31
32
  const state = persistedState as GlobalPersist;
33
+
32
34
  return {
33
35
  ...currentState,
34
36
  ...state,
@@ -36,15 +38,42 @@ const persistOptions: PersistOptions<GlobalStore, GlobalPersist> = {
36
38
  if (!draft.defaultAgent) {
37
39
  draft.defaultAgent = DEFAULT_AGENT;
38
40
  }
41
+ delete draft.enableMaxTokens;
42
+ delete draft.enableHistoryCount;
43
+ delete draft.historyCount;
44
+ delete draft.enableCompressThreshold;
45
+ delete draft.compressThreshold;
46
+
47
+ // migration to new data model
48
+ if (!draft.languageModel) {
49
+ draft.languageModel = {
50
+ openAI: {
51
+ ...DEFAULT_LLM_CONFIG.openAI,
52
+ OPENAI_API_KEY: draft.OPENAI_API_KEY || DEFAULT_LLM_CONFIG.openAI.OPENAI_API_KEY,
53
+ endpoint: draft.endpoint || DEFAULT_LLM_CONFIG.openAI.OPENAI_API_KEY,
54
+ },
55
+ };
56
+
57
+ delete draft.OPENAI_API_KEY;
58
+ delete draft.endpoint;
59
+ }
39
60
  }),
40
61
  };
41
62
  },
42
63
  name: 'LOBE_SETTINGS',
43
- partialize: (s) => ({
44
- preference: s.preference,
45
- settings: s.settings,
46
- }),
64
+
47
65
  skipHydration: true,
66
+
67
+ storage: createHyperStorage({
68
+ localStorage: {
69
+ dbName: 'LobeHub',
70
+ selectors: ['preference', 'settings'],
71
+ },
72
+ url: {
73
+ mode: 'hash',
74
+ selectors: [{ settingsTab: 'tab' }],
75
+ },
76
+ }),
48
77
  };
49
78
 
50
79
  // =============== 实装 useStore ============ //
@@ -5,15 +5,37 @@ import type { Locales } from '@/locales/resources';
5
5
  import type { LobeAgentSession } from '@/types/session';
6
6
 
7
7
  export interface GlobalBaseSettings {
8
- OPENAI_API_KEY: string;
8
+ /**
9
+ * @deprecated
10
+ */
11
+ OPENAI_API_KEY?: string;
12
+
9
13
  avatar: string;
10
- compressThreshold: number;
11
- enableCompressThreshold: boolean;
12
- enableHistoryCount: boolean;
13
- enableMaxTokens: boolean;
14
- endpoint: string;
14
+ /**
15
+ * @deprecated
16
+ */
17
+ compressThreshold?: number;
18
+ /**
19
+ * @deprecated
20
+ */
21
+ enableCompressThreshold?: boolean;
22
+ /**
23
+ * @deprecated
24
+ */
25
+ enableHistoryCount?: boolean;
26
+ /**
27
+ * @deprecated
28
+ */
29
+ enableMaxTokens?: boolean;
30
+ /**
31
+ * @deprecated
32
+ */
33
+ endpoint?: string;
15
34
  fontSize: number;
16
- historyCount: number;
35
+ /**
36
+ * @deprecated
37
+ */
38
+ historyCount?: number;
17
39
  language: Locales;
18
40
  neutralColor: NeutralColors | '';
19
41
  password: string;
@@ -21,13 +43,28 @@ export interface GlobalBaseSettings {
21
43
  themeMode: ThemeMode;
22
44
  }
23
45
 
24
- export type GlobalDefaultAgent = Partial<LobeAgentSession>;
46
+ export type GlobalDefaultAgent = Pick<LobeAgentSession, 'config' | 'meta'>;
47
+
48
+ interface OpenAIConfig {
49
+ OPENAI_API_KEY: string;
50
+ azureApiVersion?: string;
51
+ endpoint?: string;
52
+ models?: string[];
53
+ useAzure?: boolean;
54
+ }
55
+
56
+ export type GlobalLLMConfig = {
57
+ openAI: OpenAIConfig;
58
+ };
59
+
60
+ export type LLMBrand = keyof GlobalLLMConfig;
25
61
 
26
62
  /**
27
63
  * 配置设置
28
64
  */
29
65
  export interface GlobalSettings extends GlobalBaseSettings {
30
66
  defaultAgent: GlobalDefaultAgent;
67
+ languageModel: GlobalLLMConfig;
31
68
  }
32
69
 
33
70
  export type ConfigKeys = keyof GlobalSettings;
@@ -74,7 +74,7 @@ interface FetchAITaskResultParams<T> {
74
74
  /**
75
75
  * 错误处理函数
76
76
  */
77
- onError?: (e: Error) => void;
77
+ onError?: (e: Error, rawError?: any) => void;
78
78
  /**
79
79
  * 加载状态变化处理函数
80
80
  * @param loading - 是否处于加载状态
@@ -101,27 +101,19 @@ export const fetchAIFactory =
101
101
  onLoadingChange,
102
102
  abortController,
103
103
  }: FetchAITaskResultParams<T>) => {
104
- const errorHandle = (error: Error) => {
104
+ const errorHandle = (error: Error, errorContent?: any) => {
105
105
  onLoadingChange?.(false);
106
106
  if (abortController?.signal.aborted) {
107
- // notification.primaryInfo({
108
- // message: '已中断当前节点的执行任务',
109
- // });
110
107
  return;
111
108
  }
112
-
113
- // notification?.error({
114
- // message: `请求失败(${error.message})`,
115
- // placement: 'bottomRight',
116
- // });
117
- onError?.(error);
109
+ onError?.(error, errorContent);
118
110
  };
119
111
 
120
112
  onLoadingChange?.(true);
121
113
 
122
114
  const data = await fetchSSE(() => fetcher(params, { signal: abortController?.signal }), {
123
115
  onErrorHandle: (error) => {
124
- errorHandle(new Error(error.message));
116
+ errorHandle(new Error(error.message), error);
125
117
  },
126
118
  onMessageHandle,
127
119
  }).catch(errorHandle);
@@ -1,24 +0,0 @@
1
- import { LOBE_CHAT_ACCESS_CODE, OPENAI_API_KEY_HEADER_KEY, OPENAI_END_POINT } from '@/const/fetch';
2
- import { ErrorType } from '@/types/fetch';
3
- import { OpenAIStreamPayload } from '@/types/openai';
4
-
5
- import { checkAuth } from './auth';
6
- import { createErrorResponse } from './error';
7
- import { createChatCompletion } from './openai';
8
-
9
- export const runtime = 'edge';
10
-
11
- export default async function handler(req: Request) {
12
- const payload = (await req.json()) as OpenAIStreamPayload;
13
- const apiKey = req.headers.get(OPENAI_API_KEY_HEADER_KEY);
14
- const accessCode = req.headers.get(LOBE_CHAT_ACCESS_CODE);
15
- const endpoint = req.headers.get(OPENAI_END_POINT);
16
-
17
- const result = checkAuth({ accessCode, apiKey });
18
-
19
- if (!result.auth) {
20
- return createErrorResponse(result.error as ErrorType);
21
- }
22
-
23
- return createChatCompletion({ OPENAI_API_KEY: apiKey, endpoint, payload });
24
- }
@@ -1,8 +0,0 @@
1
- const isDev = process.env.NODE_ENV === 'development';
2
-
3
- const prefix = isDev ? '-dev' : '';
4
-
5
- export const URLS = {
6
- openai: '/api/openai' + prefix,
7
- plugins: '/api/plugins' + prefix,
8
- };