@lobehub/chat 0.142.0 → 0.142.2

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/.env.example CHANGED
@@ -98,6 +98,7 @@ OPENAI_API_KEY=sk-xxxxxxxxx
98
98
  ########################################
99
99
 
100
100
  #OPENROUTER_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
101
+ #OPENROUTER_CUSTOM_MODELS=model1,model2,model3
101
102
 
102
103
  ########################################
103
104
  ######### 01.AI Service ##########
package/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.142.2](https://github.com/lobehub/lobe-chat/compare/v0.142.1...v0.142.2)
6
+
7
+ <sup>Released on **2024-03-25**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Support openrouter custom models env.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Support openrouter custom models env, closes [#1647](https://github.com/lobehub/lobe-chat/issues/1647) ([78baa16](https://github.com/lobehub/lobe-chat/commit/78baa16))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 0.142.1](https://github.com/lobehub/lobe-chat/compare/v0.142.0...v0.142.1)
31
+
32
+ <sup>Released on **2024-03-25**</sup>
33
+
34
+ <br/>
35
+
36
+ <details>
37
+ <summary><kbd>Improvements and Fixes</kbd></summary>
38
+
39
+ </details>
40
+
41
+ <div align="right">
42
+
43
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
44
+
45
+ </div>
46
+
5
47
  ## [Version 0.142.0](https://github.com/lobehub/lobe-chat/compare/v0.141.2...v0.142.0)
6
48
 
7
49
  <sup>Released on **2024-03-25**</sup>
package/Dockerfile CHANGED
@@ -94,6 +94,7 @@ ENV MISTRAL_API_KEY ""
94
94
 
95
95
  # OpenRouter
96
96
  ENV OPENROUTER_API_KEY ""
97
+ ENV OPENROUTER_CUSTOM_MODELS ""
97
98
 
98
99
  # 01.AI
99
100
  ENV ZEROONE_API_KEY ""
@@ -188,5 +188,12 @@ When using the `turn` mode, the API Keys will be retrieved in a round-robin mann
188
188
  - Default: -
189
189
  - Example: `sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=`
190
190
 
191
+ ### `OPENROUTER_CUSTOM_MODELS`
192
+
193
+ - Type: Optional
194
+ - Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas.
195
+ - Default: `-`
196
+ - Example: `-all,+01-ai/yi-34b-chat,+huggingfaceh4/zephyr-7b-beta`
197
+
191
198
 
192
199
  [azure-api-verion-url]: https://docs.microsoft.com/zh-cn/azure/developer/javascript/api-reference/es-modules/azure-sdk/ai-translation/translationconfiguration?view=azure-node-latest#api-version
@@ -186,6 +186,13 @@ LobeChat 在部署时提供了丰富的模型服务商相关的环境变量,
186
186
  - 默认值:-
187
187
  - 示例:`sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=`
188
188
 
189
+ ### `OPENROUTER_CUSTOM_MODELS`
190
+
191
+ - 类型:可选
192
+ - 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。
193
+ - 默认值:`-`
194
+ - 示例:`-all,+01-ai/yi-34b-chat,+huggingfaceh4/zephyr-7b-beta`
195
+
189
196
  ## 01 AI
190
197
 
191
198
  ### `ZEROONE_API_KEY`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.142.0",
3
+ "version": "0.142.2",
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",
@@ -26,6 +26,7 @@ export const GET = async () => {
26
26
  ENABLED_ZEROONE,
27
27
  DEFAULT_AGENT_CONFIG,
28
28
  OLLAMA_CUSTOM_MODELS,
29
+ OPENROUTER_CUSTOM_MODELS,
29
30
  } = getServerConfig();
30
31
 
31
32
  const config: GlobalServerConfig = {
@@ -43,7 +44,7 @@ export const GET = async () => {
43
44
  mistral: { enabled: ENABLED_MISTRAL },
44
45
  moonshot: { enabled: ENABLED_MOONSHOT },
45
46
  ollama: { customModelName: OLLAMA_CUSTOM_MODELS, enabled: ENABLE_OLLAMA },
46
- openrouter: { enabled: ENABLED_OPENROUTER },
47
+ openrouter: { customModelName: OPENROUTER_CUSTOM_MODELS, enabled: ENABLED_OPENROUTER },
47
48
  perplexity: { enabled: ENABLED_PERPLEXITY },
48
49
  zeroone: { enabled: ENABLED_ZEROONE },
49
50
  zhipu: { enabled: ENABLED_ZHIPU },
@@ -1,5 +1,6 @@
1
1
  import { SpeedInsights } from '@vercel/speed-insights/next';
2
2
  import { ResolvingViewport } from 'next';
3
+ import { SessionProvider } from 'next-auth/react';
3
4
  import { cookies } from 'next/headers';
4
5
  import { PropsWithChildren } from 'react';
5
6
  import { isRtlLang } from 'rtl-detect';
@@ -7,41 +8,28 @@ import { isRtlLang } from 'rtl-detect';
7
8
  import Analytics from '@/components/Analytics';
8
9
  import { getServerConfig } from '@/config/server';
9
10
  import { DEFAULT_LANG, LOBE_LOCALE_COOKIE } from '@/const/locale';
10
- import {
11
- LOBE_THEME_APPEARANCE,
12
- LOBE_THEME_NEUTRAL_COLOR,
13
- LOBE_THEME_PRIMARY_COLOR,
14
- } from '@/const/theme';
15
- import Layout from '@/layout/GlobalLayout';
11
+ import GlobalProvider from '@/layout/GlobalProvider';
12
+ import { API_ENDPOINTS } from '@/services/_url';
16
13
  import { isMobileDevice } from '@/utils/responsive';
17
14
 
18
- import StyleRegistry from './StyleRegistry';
15
+ const { ENABLE_OAUTH_SSO = false } = getServerConfig();
19
16
 
20
- const { ENABLE_OAUTH_SSO } = getServerConfig();
21
-
22
- const RootLayout = ({ children }: PropsWithChildren) => {
23
- // get default theme config to use with ssr
17
+ const RootLayout = async ({ children }: PropsWithChildren) => {
24
18
  const cookieStore = cookies();
25
- const appearance = cookieStore.get(LOBE_THEME_APPEARANCE);
26
- const neutralColor = cookieStore.get(LOBE_THEME_NEUTRAL_COLOR);
27
- const primaryColor = cookieStore.get(LOBE_THEME_PRIMARY_COLOR);
19
+
28
20
  const lang = cookieStore.get(LOBE_LOCALE_COOKIE);
29
21
  const direction = isRtlLang(lang?.value || DEFAULT_LANG) ? 'rtl' : 'ltr';
30
22
 
23
+ const content = ENABLE_OAUTH_SSO ? (
24
+ <SessionProvider basePath={API_ENDPOINTS.oauth}>{children}</SessionProvider>
25
+ ) : (
26
+ children
27
+ );
28
+
31
29
  return (
32
30
  <html dir={direction} lang={lang?.value || DEFAULT_LANG} suppressHydrationWarning>
33
31
  <body>
34
- <StyleRegistry>
35
- <Layout
36
- defaultAppearance={appearance?.value}
37
- defaultLang={lang?.value}
38
- defaultNeutralColor={neutralColor?.value as any}
39
- defaultPrimaryColor={primaryColor?.value as any}
40
- enableOAuthSSO={ENABLE_OAUTH_SSO}
41
- >
42
- {children}
43
- </Layout>
44
- </StyleRegistry>
32
+ <GlobalProvider>{content}</GlobalProvider>
45
33
  <Analytics />
46
34
  <SpeedInsights />
47
35
  </body>
@@ -12,7 +12,7 @@ import AvatarWithUpload from '@/features/AvatarWithUpload';
12
12
  import { localeOptions } from '@/locales/resources';
13
13
  import { useGlobalStore } from '@/store/global';
14
14
  import { settingsSelectors } from '@/store/global/selectors';
15
- import { switchLang } from '@/utils/switchLang';
15
+ import { switchLang } from '@/utils/client/switchLang';
16
16
 
17
17
  import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from '../features/ThemeSwatches';
18
18
 
@@ -45,6 +45,7 @@ declare global {
45
45
 
46
46
  // OpenRouter Provider
47
47
  OPENROUTER_API_KEY?: string;
48
+ OPENROUTER_CUSTOM_MODELS?: string;
48
49
 
49
50
  // ZeroOne Provider
50
51
  ZEROONE_API_KEY?: string;
@@ -118,6 +119,7 @@ export const getProviderConfig = () => {
118
119
 
119
120
  ENABLED_OPENROUTER: !!OPENROUTER_API_KEY,
120
121
  OPENROUTER_API_KEY,
122
+ OPENROUTER_CUSTOM_MODELS: process.env.OPENROUTER_CUSTOM_MODELS,
121
123
 
122
124
  ENABLED_MOONSHOT: !!MOONSHOT_API_KEY,
123
125
  MOONSHOT_API_KEY,
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { Icon } from '@lobehub/ui';
2
4
  import { App, FloatButton, Spin } from 'antd';
3
5
  import { DatabaseIcon, Loader2 } from 'lucide-react';
@@ -1,7 +1,11 @@
1
+ 'use client';
2
+
1
3
  import { ConfigProvider, NeutralColors, PrimaryColors, ThemeProvider } from '@lobehub/ui';
2
- import { ThemeAppearance } from 'antd-style';
4
+ import { App } from 'antd';
5
+ import { ThemeAppearance, createStyles } from 'antd-style';
6
+ import 'antd/dist/reset.css';
3
7
  import Image from 'next/image';
4
- import { ReactNode, memo, useEffect } from 'react';
8
+ import { PropsWithChildren, ReactNode, memo, useEffect } from 'react';
5
9
 
6
10
  import {
7
11
  LOBE_THEME_APPEARANCE,
@@ -13,6 +17,25 @@ import { settingsSelectors } from '@/store/global/selectors';
13
17
  import { GlobalStyle } from '@/styles';
14
18
  import { setCookie } from '@/utils/cookie';
15
19
 
20
+ const useStyles = createStyles(({ css, token }) => ({
21
+ bg: css`
22
+ overflow-y: hidden;
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+
27
+ height: 100%;
28
+
29
+ background: ${token.colorBgLayout};
30
+ `,
31
+ }));
32
+
33
+ const Container = memo<PropsWithChildren>(({ children }) => {
34
+ const { styles } = useStyles();
35
+
36
+ return <App className={styles.bg}>{children}</App>;
37
+ });
38
+
16
39
  export interface AppThemeProps {
17
40
  children?: ReactNode;
18
41
  defaultAppearance?: ThemeAppearance;
@@ -53,7 +76,9 @@ const AppTheme = memo<AppThemeProps>(
53
76
  themeMode={themeMode}
54
77
  >
55
78
  <GlobalStyle />
56
- <ConfigProvider config={{ imgAs: Image } as any}>{children}</ConfigProvider>
79
+ <ConfigProvider config={{ imgAs: Image } as any}>
80
+ <Container>{children}</Container>
81
+ </ConfigProvider>
57
82
  </ThemeProvider>
58
83
  );
59
84
  },
@@ -1,39 +1,22 @@
1
+ 'use client';
2
+
1
3
  import { ConfigProvider } from 'antd';
2
4
  import { PropsWithChildren, memo, useEffect, useState } from 'react';
3
5
  import { isRtlLang } from 'rtl-detect';
4
- import useSWR from 'swr';
5
6
 
6
7
  import { createI18nNext } from '@/locales/create';
7
- import { normalizeLocale } from '@/locales/resources';
8
8
  import { isOnServerSide } from '@/utils/env';
9
-
10
- const getAntdLocale = async (lang?: string) => {
11
- let normalLang = normalizeLocale(lang);
12
-
13
- // due to antd only have ar-EG locale, we need to convert ar to ar-EG
14
- // refs: https://ant.design/docs/react/i18n
15
-
16
- // And we don't want to handle it in `normalizeLocale` function
17
- // because of other locale files are all `ar` not `ar-EG`
18
- if (normalLang === 'ar') normalLang = 'ar-EG';
19
-
20
- const { default: locale } = await import(`antd/locale/${normalLang.replace('-', '_')}.js`);
21
-
22
- return locale;
23
- };
9
+ import { getAntdLocale } from '@/utils/locale';
24
10
 
25
11
  interface LocaleLayoutProps extends PropsWithChildren {
12
+ antdLocale?: any;
26
13
  defaultLang?: string;
27
14
  }
28
15
 
29
- const Locale = memo<LocaleLayoutProps>(({ children, defaultLang }) => {
16
+ const Locale = memo<LocaleLayoutProps>(({ children, defaultLang, antdLocale }) => {
30
17
  const [i18n] = useState(createI18nNext(defaultLang));
31
18
  const [lang, setLang] = useState(defaultLang);
32
-
33
- const { data: locale } = useSWR(['antd-locale', lang], ([, key]) => getAntdLocale(key), {
34
- dedupingInterval: 0,
35
- revalidateOnFocus: false,
36
- });
19
+ const [locale, setLocale] = useState(antdLocale);
37
20
 
38
21
  // if run on server side, init i18n instance everytime
39
22
  if (isOnServerSide) {
@@ -49,15 +32,20 @@ const Locale = memo<LocaleLayoutProps>(({ children, defaultLang }) => {
49
32
 
50
33
  // handle i18n instance language change
51
34
  useEffect(() => {
52
- const handleLang = (e: string) => {
53
- setLang(e);
35
+ const handleLang = async (lng: string) => {
36
+ setLang(lng);
37
+
38
+ if (lang === lng) return;
39
+
40
+ const newLocale = await getAntdLocale(lng);
41
+ setLocale(newLocale);
54
42
  };
55
43
 
56
44
  i18n.instance.on('languageChanged', handleLang);
57
45
  return () => {
58
46
  i18n.instance.off('languageChanged', handleLang);
59
47
  };
60
- }, [i18n]);
48
+ }, [i18n, lang]);
61
49
 
62
50
  // detect document direction
63
51
  const documentDir = isRtlLang(lang!) ? 'rtl' : 'ltr';
@@ -69,4 +57,6 @@ const Locale = memo<LocaleLayoutProps>(({ children, defaultLang }) => {
69
57
  );
70
58
  });
71
59
 
60
+ Locale.displayName = 'Locale';
61
+
72
62
  export default Locale;
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useResponsive } from 'antd-style';
2
4
  import { useRouter } from 'next/navigation';
3
5
  import { memo, useEffect } from 'react';
@@ -0,0 +1,62 @@
1
+ import dynamic from 'next/dynamic';
2
+ import { cookies } from 'next/headers';
3
+ import { FC, ReactNode } from 'react';
4
+
5
+ import { getClientConfig } from '@/config/client';
6
+ import { LOBE_LOCALE_COOKIE } from '@/const/locale';
7
+ import {
8
+ LOBE_THEME_APPEARANCE,
9
+ LOBE_THEME_NEUTRAL_COLOR,
10
+ LOBE_THEME_PRIMARY_COLOR,
11
+ } from '@/const/theme';
12
+ import { getAntdLocale } from '@/utils/locale';
13
+
14
+ import AppTheme from './AppTheme';
15
+ import Locale from './Locale';
16
+ import StoreHydration from './StoreHydration';
17
+ import StyleRegistry from './StyleRegistry';
18
+
19
+ let DebugUI: FC = () => null;
20
+
21
+ // we need use Constant Folding to remove code below in production
22
+ // refs: https://webpack.js.org/plugins/internal-plugins/#constplugin
23
+ if (process.env.NODE_ENV === 'development') {
24
+ // eslint-disable-next-line unicorn/no-lonely-if
25
+ if (getClientConfig().DEBUG_MODE) {
26
+ DebugUI = dynamic(() => import('@/features/DebugUI'), { ssr: false }) as FC;
27
+ }
28
+ }
29
+
30
+ interface GlobalLayoutProps {
31
+ children: ReactNode;
32
+ }
33
+
34
+ const GlobalLayout = async ({ children }: GlobalLayoutProps) => {
35
+ // get default theme config to use with ssr
36
+ const cookieStore = cookies();
37
+ const appearance = cookieStore.get(LOBE_THEME_APPEARANCE);
38
+ const neutralColor = cookieStore.get(LOBE_THEME_NEUTRAL_COLOR);
39
+ const primaryColor = cookieStore.get(LOBE_THEME_PRIMARY_COLOR);
40
+
41
+ // get default locale config to use with ssr
42
+ const defaultLang = cookieStore.get(LOBE_LOCALE_COOKIE);
43
+ const antdLocale = await getAntdLocale(defaultLang?.value);
44
+
45
+ return (
46
+ <StyleRegistry>
47
+ <AppTheme
48
+ defaultAppearance={appearance?.value}
49
+ defaultNeutralColor={neutralColor?.value as any}
50
+ defaultPrimaryColor={primaryColor?.value as any}
51
+ >
52
+ <Locale antdLocale={antdLocale} defaultLang={defaultLang?.value}>
53
+ <StoreHydration />
54
+ {children}
55
+ <DebugUI />
56
+ </Locale>
57
+ </AppTheme>
58
+ </StyleRegistry>
59
+ );
60
+ };
61
+
62
+ export default GlobalLayout;
@@ -7,11 +7,11 @@ import { globalService } from '@/services/global';
7
7
  import { userService } from '@/services/user';
8
8
  import { useGlobalStore } from '@/store/global';
9
9
  import { GlobalServerConfig } from '@/types/settings';
10
- import { switchLang } from '@/utils/switchLang';
10
+ import { switchLang } from '@/utils/client/switchLang';
11
11
 
12
12
  vi.mock('zustand/traditional');
13
13
 
14
- vi.mock('@/utils/switchLang', () => ({
14
+ vi.mock('@/utils/client/switchLang', () => ({
15
15
  switchLang: vi.fn(),
16
16
  }));
17
17
 
@@ -12,10 +12,10 @@ import { UserConfig, userService } from '@/services/user';
12
12
  import type { GlobalStore } from '@/store/global';
13
13
  import type { GlobalServerConfig, GlobalSettings } from '@/types/settings';
14
14
  import { OnSyncEvent, PeerSyncStatus } from '@/types/sync';
15
+ import { switchLang } from '@/utils/client/switchLang';
15
16
  import { merge } from '@/utils/merge';
16
17
  import { browserInfo } from '@/utils/platform';
17
18
  import { setNamespace } from '@/utils/storeDebug';
18
- import { switchLang } from '@/utils/switchLang';
19
19
 
20
20
  import { preferenceSelectors } from '../preference/selectors';
21
21
  import { settingsSelectors, syncSettingsSelectors } from '../settings/selectors';
@@ -86,3 +86,145 @@ exports[`modelProviderSelectors > CUSTOM_MODELS > should work correct with gpt-4
86
86
  },
87
87
  ]
88
88
  `;
89
+
90
+ exports[`modelProviderSelectors > OPENROUTER_CUSTOM_MODELS > custom deletion, addition, and renaming of models 1`] = `
91
+ [
92
+ {
93
+ "chatModels": [
94
+ {
95
+ "description": "GPT 3.5 Turbo,适用于各种文本生成和理解任务",
96
+ "displayName": "GPT-3.5 Turbo",
97
+ "functionCall": true,
98
+ "id": "gpt-3.5-turbo",
99
+ "tokens": 16385,
100
+ },
101
+ {
102
+ "displayName": "GPT-3.5 Turbo (0125)",
103
+ "functionCall": true,
104
+ "hidden": true,
105
+ "id": "gpt-3.5-turbo-0125",
106
+ "tokens": 16385,
107
+ },
108
+ {
109
+ "displayName": "GPT-3.5 Turbo (1106)",
110
+ "functionCall": true,
111
+ "hidden": true,
112
+ "id": "gpt-3.5-turbo-1106",
113
+ "tokens": 16385,
114
+ },
115
+ {
116
+ "displayName": "GPT-3.5 Turbo Instruct",
117
+ "hidden": true,
118
+ "id": "gpt-3.5-turbo-instruct",
119
+ "tokens": 4096,
120
+ },
121
+ {
122
+ "displayName": "GPT-3.5 Turbo 16K",
123
+ "hidden": true,
124
+ "id": "gpt-3.5-turbo-16k",
125
+ "tokens": 16385,
126
+ },
127
+ {
128
+ "displayName": "GPT-3.5 Turbo (0613)",
129
+ "hidden": true,
130
+ "id": "gpt-3.5-turbo-0613",
131
+ "legacy": true,
132
+ "tokens": 4096,
133
+ },
134
+ {
135
+ "displayName": "GPT-3.5 Turbo 16K (0613)",
136
+ "hidden": true,
137
+ "id": "gpt-3.5-turbo-16k-0613",
138
+ "legacy": true,
139
+ "tokens": 4096,
140
+ },
141
+ {
142
+ "displayName": "GPT-4 Turbo Preview",
143
+ "functionCall": true,
144
+ "id": "gpt-4-turbo-preview",
145
+ "tokens": 128000,
146
+ },
147
+ {
148
+ "displayName": "GPT-4 Turbo Preview (0125)",
149
+ "functionCall": true,
150
+ "hidden": true,
151
+ "id": "gpt-4-0125-preview",
152
+ "tokens": 128000,
153
+ },
154
+ {
155
+ "description": "GPT-4 视觉预览版,支持视觉任务",
156
+ "displayName": "GPT-4 Turbo Vision Preview",
157
+ "id": "gpt-4-vision-preview",
158
+ "tokens": 128000,
159
+ "vision": true,
160
+ },
161
+ {
162
+ "displayName": "GPT-4 Turbo Preview (1106)",
163
+ "functionCall": true,
164
+ "hidden": true,
165
+ "id": "gpt-4-1106-preview",
166
+ "tokens": 128000,
167
+ },
168
+ {
169
+ "displayName": "GPT-4",
170
+ "functionCall": true,
171
+ "hidden": true,
172
+ "id": "gpt-4",
173
+ "tokens": 8192,
174
+ },
175
+ {
176
+ "displayName": "GPT-4 (0613)",
177
+ "functionCall": true,
178
+ "hidden": true,
179
+ "id": "gpt-4-0613",
180
+ "tokens": 8192,
181
+ },
182
+ {
183
+ "displayName": "GPT-4 32K",
184
+ "functionCall": true,
185
+ "hidden": true,
186
+ "id": "gpt-4-32k",
187
+ "tokens": 32768,
188
+ },
189
+ {
190
+ "displayName": "GPT-4 32K (0613)",
191
+ "functionCall": true,
192
+ "hidden": true,
193
+ "id": "gpt-4-32k-0613",
194
+ "tokens": 32768,
195
+ },
196
+ {
197
+ "displayName": "GPT-4 ALL",
198
+ "files": true,
199
+ "functionCall": true,
200
+ "hidden": true,
201
+ "id": "gpt-4-all",
202
+ "tokens": 32768,
203
+ "vision": true,
204
+ },
205
+ ],
206
+ "enabled": true,
207
+ "id": "openai",
208
+ },
209
+ {
210
+ "chatModels": [
211
+ {
212
+ "displayName": "google/gemma-7b-it",
213
+ "functionCall": true,
214
+ "id": "google/gemma-7b-it",
215
+ "isCustom": true,
216
+ "vision": true,
217
+ },
218
+ {
219
+ "displayName": "Mistral-7B-Instruct",
220
+ "functionCall": true,
221
+ "id": "mistralai/mistral-7b-instruct",
222
+ "isCustom": true,
223
+ "vision": true,
224
+ },
225
+ ],
226
+ "enabled": true,
227
+ "id": "openrouter",
228
+ },
229
+ ]
230
+ `;
@@ -131,6 +131,32 @@ describe('modelProviderSelectors', () => {
131
131
  });
132
132
  });
133
133
 
134
+ describe('OPENROUTER_CUSTOM_MODELS', () => {
135
+ it('custom deletion, addition, and renaming of models', () => {
136
+ const s = merge(initialSettingsState, {
137
+ settings: {
138
+ languageModel: {
139
+ openrouter: {
140
+ enabled: true,
141
+ },
142
+ }
143
+ },
144
+ serverConfig: {
145
+ languageModel: {
146
+ openrouter: {
147
+ apiKey: 'test-openrouter-api-key',
148
+ customModelName:
149
+ '-all,+google/gemma-7b-it,+mistralai/mistral-7b-instruct=Mistral-7B-Instruct',
150
+ },
151
+ }
152
+ },
153
+ }) as unknown as GlobalStore;
154
+
155
+ const result = modelProviderSelectors.modelSelectList(s).filter((r) => r.enabled);
156
+ expect(result).toMatchSnapshot();
157
+ });
158
+ });
159
+
134
160
  describe('modelEnabledVision', () => {
135
161
  it('should return true if the model has vision ability', () => {
136
162
  const hasAbility = modelProviderSelectors.modelEnabledVision('gpt-4-vision-preview')(
@@ -149,9 +149,15 @@ const modelSelectList = (s: GlobalStore): ModelProviderCard[] => {
149
149
 
150
150
  const ollamaChatModels = processChatModels(ollamaModelConfig, OllamaProvider.chatModels);
151
151
 
152
- const openrouterModelConfig = parseModelString(
153
- currentSettings(s).languageModel.openrouter.customModelName,
154
- )
152
+ const openrouterModelString = [
153
+ s.serverConfig.languageModel?.openrouter?.customModelName,
154
+ currentSettings(s).languageModel.openrouter.customModelName
155
+ ]
156
+ .filter(Boolean)
157
+ .join(',');
158
+
159
+ const openrouterModelConfig = parseModelString(openrouterModelString);
160
+
155
161
  const openrouterChatModels = processChatModels(openrouterModelConfig, OpenRouterProvider.chatModels);
156
162
 
157
163
  return [
@@ -0,0 +1,16 @@
1
+ import { normalizeLocale } from '@/locales/resources';
2
+
3
+ export const getAntdLocale = async (lang?: string) => {
4
+ let normalLang = normalizeLocale(lang);
5
+
6
+ // due to antd only have ar-EG locale, we need to convert ar to ar-EG
7
+ // refs: https://ant.design/docs/react/i18n
8
+
9
+ // And we don't want to handle it in `normalizeLocale` function
10
+ // because of other locale files are all `ar` not `ar-EG`
11
+ if (normalLang === 'ar') normalLang = 'ar-EG';
12
+
13
+ const { default: locale } = await import(`antd/locale/${normalLang.replace('-', '_')}.js`);
14
+
15
+ return locale;
16
+ };
@@ -1,75 +0,0 @@
1
- 'use client';
2
-
3
- import { App } from 'antd';
4
- import { createStyles } from 'antd-style';
5
- import 'antd/dist/reset.css';
6
- import { SessionProvider } from 'next-auth/react';
7
- import dynamic from 'next/dynamic';
8
- import { FC, PropsWithChildren, memo } from 'react';
9
-
10
- import { getClientConfig } from '@/config/client';
11
- import { API_ENDPOINTS } from '@/services/_url';
12
-
13
- import AppTheme, { AppThemeProps } from './AppTheme';
14
- import Locale from './Locale';
15
- import StoreHydration from './StoreHydration';
16
-
17
- let DebugUI: FC = () => null;
18
-
19
- // we need use Constant Folding to remove code below in production
20
- // refs: https://webpack.js.org/plugins/internal-plugins/#constplugin
21
- if (process.env.NODE_ENV === 'development') {
22
- // eslint-disable-next-line unicorn/no-lonely-if
23
- if (getClientConfig().DEBUG_MODE) {
24
- DebugUI = dynamic(() => import('@/features/DebugUI'), { ssr: false }) as FC;
25
- }
26
- }
27
-
28
- const useStyles = createStyles(({ css, token }) => ({
29
- bg: css`
30
- overflow-y: hidden;
31
- display: flex;
32
- flex-direction: column;
33
- align-items: center;
34
-
35
- height: 100%;
36
-
37
- background: ${token.colorBgLayout};
38
- `,
39
- }));
40
-
41
- const Container = memo<PropsWithChildren>(({ children }) => {
42
- const { styles } = useStyles();
43
-
44
- return <App className={styles.bg}>{children}</App>;
45
- });
46
-
47
- interface GlobalLayoutProps extends AppThemeProps {
48
- defaultLang?: string;
49
- enableOAuthSSO?: boolean;
50
- }
51
-
52
- const GlobalLayout = ({
53
- children,
54
- defaultLang,
55
- enableOAuthSSO = false,
56
- ...theme
57
- }: GlobalLayoutProps) => {
58
- const content = (
59
- <AppTheme {...theme}>
60
- <Locale defaultLang={defaultLang}>
61
- <StoreHydration />
62
- <Container>{children}</Container>
63
- <DebugUI />
64
- </Locale>
65
- </AppTheme>
66
- );
67
-
68
- return enableOAuthSSO ? (
69
- <SessionProvider basePath={API_ENDPOINTS.oauth}>{content}</SessionProvider>
70
- ) : (
71
- content
72
- );
73
- };
74
-
75
- export default GlobalLayout;