diff --git a/.env.example b/.env.example
index b102cf93c13e..4ec9439d91c5 100644
--- a/.env.example
+++ b/.env.example
@@ -2,7 +2,7 @@
ACCESS_CODE=lobe66
# add your custom model name, multi model separate by comma. for example gpt-3.5-1106,gpt-4-1106
-# NEXT_PUBLIC_CUSTOM_MODELS=model1,model2,model3
+# CUSTOM_MODELS=model1,model2,model3
# ---- only choose one from OpenAI Service and Azure OpenAI Service ---- #
diff --git a/Dockerfile b/Dockerfile
index 03f16e8fe1af..c04632819311 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -58,7 +58,7 @@ ENV PORT=3210
# General Variables
ENV ACCESS_CODE "lobe66"
-ENV NEXT_PUBLIC_CUSTOM_MODELS ""
+ENV CUSTOM_MODELS ""
# OpenAI
ENV OPENAI_API_KEY ""
diff --git a/README.md b/README.md
index 29e4910d8620..eb454c1ad15e 100644
--- a/README.md
+++ b/README.md
@@ -391,12 +391,12 @@ $ docker run -d -p 3210:3210 \
This project provides some additional configuration items set with environment variables:
-| Environment Variable | Required | Description | Example |
-| ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
-| `OPENAI_API_KEY` | Yes | This is the API key you apply on the OpenAI account page | `sk-xxxxxx...xxxxxx` |
-| `OPENAI_PROXY_URL` | No | If you manually configure the OpenAI interface proxy, you can use this configuration item to override the default OpenAI API request base URL | `https://api.chatanywhere.cn/v1`
The default value is
`https://api.openai.com/v1` |
-| `OPENAI_FUNCTION_REGIONS` | No | When you deploy Lobe-Chat using Vercel and need to specify the region for the Edge Function that handles requests to the OpenAI API, you can use this configuration. The value should be a comma-separated array of strings. | `iad1,sfo1` |
-| `ACCESS_CODE` | No | Add a password to access this service; you can set a long password to avoid leaking. If this value contains a comma, it is a password array. | `awCTe)re_r74` or `rtrt_ewee3@09!` or `code1,code2,code3` |
+| Environment Variable | Required | Description | Example |
+| -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
+| `OPENAI_API_KEY` | Yes | This is the API key you apply on the OpenAI account page | `sk-xxxxxx...xxxxxx` |
+| `OPENAI_PROXY_URL` | No | If you manually configure the OpenAI interface proxy, you can use this configuration item to override the default OpenAI API request base URL | `https://api.chatanywhere.cn/v1`
The default value is
`https://api.openai.com/v1` |
+| `ACCESS_CODE` | No | Add a password to access this service; you can set a long password to avoid leaking. If this value contains a comma, it is a password array. | `awCTe)re_r74` or `rtrt_ewee3@09!` or `code1,code2,code3` |
+| `CUSTOM_MODELS` | No | Used to control the model list. Use `+` to add a model, `-` to hide a model, and `model_name=display_name` to customize the display name of a model, separated by commas. | `qwen-7b-chat,+glm-6b,-gpt-3.5-turbo` |
> \[!NOTE]
>
diff --git a/README.zh-CN.md b/README.zh-CN.md
index f4079698e5f9..2bd91f05b65c 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -363,12 +363,12 @@ $ docker run -d -p 3210:3210 \
本项目提供了一些额外的配置项,使用环境变量进行设置:
-| 环境变量 | 类型 | 描述 | 示例 |
-| ------------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
-| `OPENAI_API_KEY` | 必选 | 这是你在 OpenAI 账户页面申请的 API 密钥 | `sk-xxxxxx...xxxxxx` |
-| `OPENAI_PROXY_URL` | 可选 | 如果你手动配置了 OpenAI 接口代理,可以使用此配置项来覆盖默认的 OpenAI API 请求基础 URL | `https://api.chatanywhere.cn/v1`
默认值:
`https://api.openai.com/v1` |
-| `OPENAI_FUNCTION_REGIONS` | 可选 | 当你使用 Vercel 部署 Lobe-Chat,而且有需求指定响应调用 OpenAI 接口的请求的 Edge Function 的 Region 时,可以使用该配置进行配置,该值的类型为逗号分隔的字符串数组 | `iad1,sfo1` |
-| `ACCESS_CODE` | 可选 | 添加访问此服务的密码,你可以设置一个长密码以防被爆破,该值用逗号分隔时为密码数组 | `awCTe)re_r74` or `rtrt_ewee3@09!` or `code1,code2,code3` |
+| 环境变量 | 类型 | 描述 | 示例 |
+| ------------------ | ---- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
+| `OPENAI_API_KEY` | 必选 | 这是你在 OpenAI 账户页面申请的 API 密钥 | `sk-xxxxxx...xxxxxx` |
+| `OPENAI_PROXY_URL` | 可选 | 如果你手动配置了 OpenAI 接口代理,可以使用此配置项来覆盖默认的 OpenAI API 请求基础 URL | `https://api.chatanywhere.cn/v1`
默认值:
`https://api.openai.com/v1` |
+| `ACCESS_CODE` | 可选 | 添加访问此服务的密码,你可以设置一个长密码以防被爆破,该值用逗号分隔时为密码数组 | `awCTe)re_r74` or `rtrt_ewee3@09!` or `code1,code2,code3` |
+| `CUSTOM_MODELS` | 可选 | 用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。 | `qwen-7b-chat,+glm-6b,-gpt-3.5-turbo` |
> \[!NOTE]
>
diff --git a/docs/Deployment/Environment-Variable.md b/docs/Deployment/Environment-Variable.md
index ce195e617d9c..79b26fae84c8 100644
--- a/docs/Deployment/Environment-Variable.md
+++ b/docs/Deployment/Environment-Variable.md
@@ -6,7 +6,7 @@ LobeChat provides additional configuration options during deployment, which can
- [General Variables](#general-variables)
- [`ACCESS_CODE`](#access_code)
- - [`NEXT_PUBLIC_CUSTOM_MODELS`](#next_public_custom_models)
+ - [`CUSTOM_MODELS`](#custom_models)
- [OpenAI](#openai)
- [`OPENAI_API_KEY`](#openai_api_key)
- [`OPENAI_PROXY_URL`](#openai_proxy_url)
@@ -31,7 +31,7 @@ LobeChat provides additional configuration options during deployment, which can
- Default: `-`
- Example: `awCTe)re_r74` or `rtrt_ewee3@09!` or `code1,code2,code3`
-### `NEXT_PUBLIC_CUSTOM_MODELS`
+### `CUSTOM_MODELS`
- Type: Optional
- Description: Used to control the model list. Use `+` to add a model, `-` to hide a model, and `model_name=display_name` to customize the display name of a model, separated by commas.
diff --git a/docs/Deployment/Environment-Variable.zh-CN.md b/docs/Deployment/Environment-Variable.zh-CN.md
index 9def8293e5f5..24011fcfc6d6 100644
--- a/docs/Deployment/Environment-Variable.zh-CN.md
+++ b/docs/Deployment/Environment-Variable.zh-CN.md
@@ -6,7 +6,7 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
- [通用变量](#通用变量)
- [`ACCESS_CODE`](#access_code)
- - [`NEXT_PUBLIC_CUSTOM_MODELS`](#next_public_custom_models)
+ - [`CUSTOM_MODELS`](#custom_models)
- [OpenAI](#openai)
- [`OPENAI_API_KEY`](#openai_api_key)
- [`OPENAI_PROXY_URL`](#openai_proxy_url)
@@ -31,7 +31,7 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
- 默认值:-
- 示例:`awCTe)re_r74` or `rtrt_ewee3@09!`
-### `NEXT_PUBLIC_CUSTOM_MODELS`
+### `CUSTOM_MODELS`
- 类型:可选
- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名` 来自定义模型的展示名,用英文逗号隔开。
diff --git a/next.config.mjs b/next.config.mjs
index aace97764136..1c60e8d3d6b7 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -19,10 +19,6 @@ const withPWA = nextPWA({
/** @type {import('next').NextConfig} */
const nextConfig = {
compress: isProd,
- env: {
- AGENTS_INDEX_URL: process.env.AGENTS_INDEX_URL ?? '',
- PLUGINS_INDEX_URL: process.env.PLUGINS_INDEX_URL ?? '',
- },
experimental: {
forceSwcTransforms: true,
optimizePackageImports: [
diff --git a/src/app/api/config/route.ts b/src/app/api/config/route.ts
new file mode 100644
index 000000000000..61db882bc305
--- /dev/null
+++ b/src/app/api/config/route.ts
@@ -0,0 +1,15 @@
+import { getServerConfig } from '@/config/server';
+import { GlobalServerConfig } from '@/types/settings';
+
+export const runtime = 'edge';
+
+/**
+ * get Server config to client
+ */
+export const GET = async () => {
+ const { CUSTOM_MODELS } = getServerConfig();
+
+ const config: GlobalServerConfig = { customModelName: CUSTOM_MODELS };
+
+ return new Response(JSON.stringify(config));
+};
diff --git a/src/app/api/market/AgentMarket.ts b/src/app/api/market/AgentMarket.ts
new file mode 100644
index 000000000000..35eaf2087828
--- /dev/null
+++ b/src/app/api/market/AgentMarket.ts
@@ -0,0 +1,25 @@
+import urlJoin from 'url-join';
+
+import { getServerConfig } from '@/config/server';
+import { DEFAULT_LANG, checkLang } from '@/const/locale';
+import { Locales } from '@/locales/resources';
+
+export class AgentMarket {
+ private readonly baseUrl: string;
+
+ constructor(baseUrl?: string) {
+ this.baseUrl = baseUrl || getServerConfig().AGENTS_INDEX_URL;
+ }
+
+ getAgentIndexUrl = (lang: Locales = DEFAULT_LANG) => {
+ if (checkLang(lang)) return this.baseUrl;
+
+ return urlJoin(this.baseUrl, `index.${lang}.json`);
+ };
+
+ getAgentUrl = (identifier: string, lang: Locales = DEFAULT_LANG) => {
+ if (checkLang(lang)) return urlJoin(this.baseUrl, `${identifier}.json`);
+
+ return urlJoin(this.baseUrl, `${identifier}.${lang}.json`);
+ };
+}
diff --git a/src/app/api/market/[id]/route.ts b/src/app/api/market/[id]/route.ts
index e8185561f0d4..943d6f688352 100644
--- a/src/app/api/market/[id]/route.ts
+++ b/src/app/api/market/[id]/route.ts
@@ -1,5 +1,6 @@
import { DEFAULT_LANG } from '@/const/locale';
-import { getAgentJSON } from '@/const/url';
+
+import { AgentMarket } from '../AgentMarket';
export const runtime = 'edge';
@@ -8,11 +9,13 @@ export const GET = async (req: Request, { params }: { params: { id: string } })
const locale = searchParams.get('locale');
+ const market = new AgentMarket();
+
let res: Response;
- res = await fetch(getAgentJSON(params.id, locale as any));
+ res = await fetch(market.getAgentUrl(params.id, locale as any));
if (res.status === 404) {
- res = await fetch(getAgentJSON(params.id, DEFAULT_LANG));
+ res = await fetch(market.getAgentUrl(params.id, DEFAULT_LANG));
}
return res;
diff --git a/src/app/api/market/route.ts b/src/app/api/market/route.ts
index 933e262b4e5d..4ac0e8868ea4 100644
--- a/src/app/api/market/route.ts
+++ b/src/app/api/market/route.ts
@@ -1,17 +1,20 @@
import { DEFAULT_LANG } from '@/const/locale';
-import { getAgentIndexJSON } from '@/const/url';
+
+import { AgentMarket } from './AgentMarket';
export const runtime = 'edge';
export const GET = async (req: Request) => {
const locale = new URL(req.url).searchParams.get('locale');
+ const market = new AgentMarket();
+
let res: Response;
- res = await fetch(getAgentIndexJSON(locale as any));
+ res = await fetch(market.getAgentIndexUrl(locale as any));
if (res.status === 404) {
- res = await fetch(getAgentIndexJSON(DEFAULT_LANG));
+ res = await fetch(market.getAgentIndexUrl(DEFAULT_LANG));
}
return res;
diff --git a/src/components/FileList/EditableFileList.tsx b/src/components/FileList/EditableFileList.tsx
index 37d9193240ad..6fd738bbe83a 100644
--- a/src/components/FileList/EditableFileList.tsx
+++ b/src/components/FileList/EditableFileList.tsx
@@ -3,7 +3,7 @@ import { useResponsive } from 'antd-style';
import { memo } from 'react';
import { Flexbox } from 'react-layout-kit';
-import ImageFileItem from '@/components/FileList/ImageFileItem';
+import ImageFileItem from './ImageFileItem';
interface EditableFileListProps {
alwaysShowClose?: boolean;
diff --git a/src/components/FileList/FileGrid.tsx b/src/components/FileList/FileGrid.tsx
deleted file mode 100644
index 4239d6d93415..000000000000
--- a/src/components/FileList/FileGrid.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { CSSProperties, ReactNode, memo } from 'react';
-import { Flexbox } from 'react-layout-kit';
-
-import { MAX_SIZE_DESKTOP, MIN_IMAGE_SIZE, useStyles } from '@/components/FileList/style';
-
-interface FileGridProps {
- children: ReactNode;
- className?: string;
- col?: number;
- gap?: number;
- max?: number;
- min?: number;
- style?: CSSProperties;
-}
-
-const FileGrid = memo(
- ({
- gap = 4,
- col = 3,
- max = MAX_SIZE_DESKTOP,
- min = MIN_IMAGE_SIZE,
- children,
- className,
- style,
- }) => {
- const { styles, cx } = useStyles({ col, gap, max, min });
-
- return (
-
- {children}
-
- );
- },
-);
-
-export default FileGrid;
diff --git a/src/components/FileList/index.tsx b/src/components/FileList/index.tsx
index 197c9582dd66..ce83e9e2cd4c 100644
--- a/src/components/FileList/index.tsx
+++ b/src/components/FileList/index.tsx
@@ -1,11 +1,8 @@
import { ImageGallery } from '@lobehub/ui';
-import { useResponsive } from 'antd-style';
-import { memo, useMemo } from 'react';
-import { Flexbox } from 'react-layout-kit';
+import { memo } from 'react';
-import { MAX_SIZE_DESKTOP, MAX_SIZE_MOBILE } from '@/components/FileList/style';
+import GalleyGrid from '@/components/GalleyGrid';
-import FileGrid from './FileGrid';
import ImageFileItem from './ImageFileItem';
interface FileListProps {
@@ -13,48 +10,9 @@ interface FileListProps {
}
const FileList = memo(({ items }) => {
- const { mobile } = useResponsive();
-
- const { firstRow, lastRow } = useMemo(() => {
- if (items.length === 4) {
- return {
- firstRow: items.slice(0, 2),
- lastRow: items.slice(2, 4),
- };
- }
-
- const firstCol = items.length % 3 === 0 ? 3 : items.length % 3;
-
- return {
- firstRow: items.slice(0, firstCol),
- lastRow: items.slice(firstCol, items.length),
- };
- }, [items]);
-
- const { gap, max } = useMemo(
- () => ({
- gap: mobile ? 4 : 6,
- max: mobile ? MAX_SIZE_MOBILE : MAX_SIZE_DESKTOP,
- }),
- [mobile],
- );
-
return (
-
-
- {firstRow.map((i) => (
-
- ))}
-
- {lastRow.length > 0 && (
- 2 ? 3 : lastRow.length} gap={gap} max={max}>
- {lastRow.map((i) => (
-
- ))}
-
- )}
-
+
);
});
diff --git a/src/components/FileList/style.ts b/src/components/FileList/style.ts
index 1f8e5f1601e1..1bb5a495a4f7 100644
--- a/src/components/FileList/style.ts
+++ b/src/components/FileList/style.ts
@@ -1,24 +1 @@
-import { createStyles } from 'antd-style';
-
export const MIN_IMAGE_SIZE = 64;
-export const MAX_SIZE_DESKTOP = 640;
-export const MAX_SIZE_MOBILE = 280;
-export const useStyles = createStyles(
- ({ css }, { col, gap, max, min }: { col: number; gap: number; max: number; min: number }) => ({
- container: css`
- display: grid;
- grid-gap: ${gap}px;
- grid-template-columns: repeat(${col}, 1fr);
-
- width: 100%;
- max-width: ${max}px;
-
- & > div {
- width: 100%;
- min-width: ${min}px;
- min-height: ${min}px;
- max-height: ${(max - gap * (col - 1)) / col}px;
- }
- `,
- }),
-);
diff --git a/src/config/__tests__/client.test.ts b/src/config/__tests__/client.test.ts
index 84ce92b4943e..0399e2723509 100644
--- a/src/config/__tests__/client.test.ts
+++ b/src/config/__tests__/client.test.ts
@@ -9,29 +9,6 @@ vi.stubGlobal('process', {
});
describe('getClientConfig', () => {
- it('should return default URLs when no environment variables are set', () => {
- const config = getClientConfig();
- expect(config.AGENTS_INDEX_URL).toBe('https://chat-agents.lobehub.com');
- expect(config.PLUGINS_INDEX_URL).toBe('https://chat-plugins.lobehub.com');
- });
-
- it('should return custom URLs when environment variables are set', () => {
- process.env.AGENTS_INDEX_URL = 'https://custom-agents-url.com';
- process.env.PLUGINS_INDEX_URL = 'https://custom-plugins-url.com';
- const config = getClientConfig();
- expect(config.AGENTS_INDEX_URL).toBe('https://custom-agents-url.com');
- expect(config.PLUGINS_INDEX_URL).toBe('https://custom-plugins-url.com');
- });
-
- it('should return default URLs when environment variables are empty string', () => {
- process.env.AGENTS_INDEX_URL = '';
- process.env.PLUGINS_INDEX_URL = '';
-
- const config = getClientConfig();
- expect(config.AGENTS_INDEX_URL).toBe('https://chat-agents.lobehub.com');
- expect(config.PLUGINS_INDEX_URL).toBe('https://chat-plugins.lobehub.com');
- });
-
it('should correctly reflect boolean values for analytics flags', () => {
process.env.NEXT_PUBLIC_ANALYTICS_VERCEL = '1';
process.env.NEXT_PUBLIC_VERCEL_DEBUG = '1';
diff --git a/src/config/__tests__/server.test.ts b/src/config/__tests__/server.test.ts
index bda70cfcace4..21c31bec6903 100644
--- a/src/config/__tests__/server.test.ts
+++ b/src/config/__tests__/server.test.ts
@@ -21,7 +21,7 @@ describe('getServerConfig', () => {
global.process = undefined;
expect(() => getServerConfig()).toThrow(
- '[Server Config] you are importing a nodejs-only module outside of nodejs',
+ '[Server Config] you are importing a server-only module outside of server',
);
global.process = originalProcess; // Restore the original process object
@@ -55,4 +55,29 @@ describe('getServerConfig', () => {
const config = getServerConfig();
expect(config.IMGUR_CLIENT_ID).toBe('custom-client-id');
});
+
+ describe('index url', () => {
+ it('should return default URLs when no environment variables are set', () => {
+ const config = getServerConfig();
+ expect(config.AGENTS_INDEX_URL).toBe('https://chat-agents.lobehub.com');
+ expect(config.PLUGINS_INDEX_URL).toBe('https://chat-plugins.lobehub.com');
+ });
+
+ it('should return custom URLs when environment variables are set', () => {
+ process.env.AGENTS_INDEX_URL = 'https://custom-agents-url.com';
+ process.env.PLUGINS_INDEX_URL = 'https://custom-plugins-url.com';
+ const config = getServerConfig();
+ expect(config.AGENTS_INDEX_URL).toBe('https://custom-agents-url.com');
+ expect(config.PLUGINS_INDEX_URL).toBe('https://custom-plugins-url.com');
+ });
+
+ it('should return default URLs when environment variables are empty string', () => {
+ process.env.AGENTS_INDEX_URL = '';
+ process.env.PLUGINS_INDEX_URL = '';
+
+ const config = getServerConfig();
+ expect(config.AGENTS_INDEX_URL).toBe('https://chat-agents.lobehub.com');
+ expect(config.PLUGINS_INDEX_URL).toBe('https://chat-plugins.lobehub.com');
+ });
+ });
});
diff --git a/src/config/client.ts b/src/config/client.ts
index f770496a5356..abc9348edf0b 100644
--- a/src/config/client.ts
+++ b/src/config/client.ts
@@ -1,14 +1,13 @@
+/**
+ * the client config is only used in Vercel deployment
+ */
+
/* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface ProcessEnv {
- AGENTS_INDEX_URL?: string;
- PLUGINS_INDEX_URL?: string;
-
- NEXT_PUBLIC_CUSTOM_MODELS?: string;
-
NEXT_PUBLIC_ANALYTICS_VERCEL?: string;
NEXT_PUBLIC_VERCEL_DEBUG?: string;
@@ -35,16 +34,6 @@ declare global {
}
export const getClientConfig = () => ({
- AGENTS_INDEX_URL: !!process.env.AGENTS_INDEX_URL
- ? process.env.AGENTS_INDEX_URL
- : 'https://chat-agents.lobehub.com',
- PLUGINS_INDEX_URL: !!process.env.PLUGINS_INDEX_URL
- ? process.env.PLUGINS_INDEX_URL
- : 'https://chat-plugins.lobehub.com',
-
- // custom model names
- CUSTOM_MODELS: process.env.NEXT_PUBLIC_CUSTOM_MODELS,
-
// Vercel Analytics
ANALYTICS_VERCEL: process.env.NEXT_PUBLIC_ANALYTICS_VERCEL === '1',
VERCEL_DEBUG: process.env.NEXT_PUBLIC_VERCEL_DEBUG === '1',
diff --git a/src/config/server.ts b/src/config/server.ts
index d068f182a0b6..673d3f9efb78 100644
--- a/src/config/server.ts
+++ b/src/config/server.ts
@@ -5,6 +5,7 @@ declare global {
namespace NodeJS {
interface ProcessEnv {
ACCESS_CODE?: string;
+ CUSTOM_MODELS?: string;
OPENAI_API_KEY?: string;
OPENAI_PROXY_URL?: string;
@@ -14,6 +15,9 @@ declare global {
USE_AZURE_OPENAI?: string;
IMGUR_CLIENT_ID?: string;
+
+ AGENTS_INDEX_URL?: string;
+ PLUGINS_INDEX_URL?: string;
}
}
}
@@ -24,7 +28,7 @@ const DEFAULT_IMAGUR_CLIENT_ID = 'e415f320d6e24f9';
export const getServerConfig = () => {
if (typeof process === 'undefined') {
- throw new Error('[Server Config] you are importing a nodejs-only module outside of nodejs');
+ throw new Error('[Server Config] you are importing a server-only module outside of server');
}
// region format: iad1,sfo1
@@ -37,6 +41,7 @@ export const getServerConfig = () => {
return {
ACCESS_CODES,
+ CUSTOM_MODELS: process.env.CUSTOM_MODELS,
SHOW_ACCESS_CODE_CONFIG: !!ACCESS_CODES.length,
@@ -49,5 +54,13 @@ export const getServerConfig = () => {
USE_AZURE_OPENAI: process.env.USE_AZURE_OPENAI === '1',
IMGUR_CLIENT_ID: process.env.IMGUR_CLIENT_ID || DEFAULT_IMAGUR_CLIENT_ID,
+
+ AGENTS_INDEX_URL: !!process.env.AGENTS_INDEX_URL
+ ? process.env.AGENTS_INDEX_URL
+ : 'https://chat-agents.lobehub.com',
+
+ PLUGINS_INDEX_URL: !!process.env.PLUGINS_INDEX_URL
+ ? process.env.PLUGINS_INDEX_URL
+ : 'https://chat-plugins.lobehub.com',
};
};
diff --git a/src/const/settings.ts b/src/const/settings.ts
index 6fd1eca950ba..e53cd9e6e081 100644
--- a/src/const/settings.ts
+++ b/src/const/settings.ts
@@ -1,4 +1,3 @@
-import { getClientConfig } from '@/config/client';
import { DEFAULT_OPENAI_MODEL_LIST } from '@/const/llm';
import { DEFAULT_AGENT_META } from '@/const/meta';
import { LobeAgentConfig, LobeAgentTTSConfig } from '@/types/agent';
@@ -52,8 +51,6 @@ export const DEFAULT_AGENT_CONFIG: LobeAgentConfig = {
export const DEFAULT_LLM_CONFIG: GlobalLLMConfig = {
openAI: {
OPENAI_API_KEY: '',
- // support user custom model names with env var
- customModelName: getClientConfig().CUSTOM_MODELS,
models: DEFAULT_OPENAI_MODEL_LIST,
},
};
diff --git a/src/const/url.ts b/src/const/url.ts
index 4b5ec0298f05..8847bb4491ce 100644
--- a/src/const/url.ts
+++ b/src/const/url.ts
@@ -1,6 +1,5 @@
import urlJoin from 'url-join';
-import { getClientConfig } from '@/config/client';
import { Locales } from '@/locales/resources';
import pkg from '../../package.json';
@@ -15,7 +14,7 @@ export const ABOUT = pkg.homepage;
export const FEEDBACK = pkg.bugs.url;
export const DISCORD = 'https://discord.gg/AYFPHvv2jT';
-export const { PLUGINS_INDEX_URL, AGENTS_INDEX_URL } = getClientConfig();
+export const PLUGINS_INDEX_URL = 'https://chat-plugins.lobehub.com';
export const getPluginIndexJSON = (lang: Locales = DEFAULT_LANG, baseUrl = PLUGINS_INDEX_URL) => {
if (checkLang(lang)) return baseUrl;
@@ -23,22 +22,6 @@ export const getPluginIndexJSON = (lang: Locales = DEFAULT_LANG, baseUrl = PLUGI
return urlJoin(baseUrl, `index.${lang}.json`);
};
-export const getAgentIndexJSON = (lang: Locales = DEFAULT_LANG, baseUrl = AGENTS_INDEX_URL) => {
- if (checkLang(lang)) return baseUrl;
-
- return urlJoin(baseUrl, `index.${lang}.json`);
-};
-
-export const getAgentJSON = (
- identifier: string,
- lang: Locales = DEFAULT_LANG,
- baseUrl = AGENTS_INDEX_URL,
-) => {
- if (checkLang(lang)) return urlJoin(baseUrl, `${identifier}.json`);
-
- return urlJoin(baseUrl, `${identifier}.${lang}.json`);
-};
-
export const AGENTS_INDEX_GITHUB = 'https://github.com/lobehub/lobe-chat-agents';
export const AGENTS_INDEX_GITHUB_ISSUE = urlJoin(AGENTS_INDEX_GITHUB, 'issues/new');
diff --git a/src/layout/GlobalLayout/StoreHydration.tsx b/src/layout/GlobalLayout/StoreHydration.tsx
index 89d87b97f2d9..f58bdc6488d2 100644
--- a/src/layout/GlobalLayout/StoreHydration.tsx
+++ b/src/layout/GlobalLayout/StoreHydration.tsx
@@ -8,12 +8,16 @@ import { useEffectAfterSessionHydrated, useSessionStore } from '@/store/session'
const StoreHydration = memo(() => {
const router = useRouter();
+ const useFetchGlobalConfig = useGlobalStore((s) => s.useFetchGlobalConfig);
+
useEffect(() => {
// refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
useSessionStore.persist.rehydrate();
useGlobalStore.persist.rehydrate();
}, []);
+ useFetchGlobalConfig();
+
const { mobile } = useResponsive();
useEffectAfterSessionHydrated(
@@ -29,6 +33,7 @@ const StoreHydration = memo(() => {
},
[router],
);
+
useEffect(() => {
router.prefetch('/chat');
router.prefetch('/market');
diff --git a/src/services/__tests__/chat.test.ts b/src/services/__tests__/chat.test.ts
index 6fe0874c1c2c..4d137bef2e1e 100644
--- a/src/services/__tests__/chat.test.ts
+++ b/src/services/__tests__/chat.test.ts
@@ -1,6 +1,6 @@
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
import { act } from '@testing-library/react';
-import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import { describe, expect, it, vi } from 'vitest';
import { VISION_MODEL_WHITE_LIST } from '@/const/llm';
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
diff --git a/src/services/__tests__/global.test.ts b/src/services/__tests__/global.test.ts
index 897b9ff79691..41d49aa6b0f4 100644
--- a/src/services/__tests__/global.test.ts
+++ b/src/services/__tests__/global.test.ts
@@ -9,49 +9,66 @@ beforeEach(() => {
});
describe('GlobalService', () => {
- it('should return the latest version when fetch is successful', async () => {
- // Arrange
- const mockVersion = '1.0.0';
- (fetch as Mock).mockResolvedValue({
- json: () => Promise.resolve({ 'dist-tags': { latest: mockVersion } }),
+ describe('getLatestVersion', () => {
+ it('should return the latest version when fetch is successful', async () => {
+ // Arrange
+ const mockVersion = '1.0.0';
+ (fetch as Mock).mockResolvedValue({
+ json: () => Promise.resolve({ 'dist-tags': { latest: mockVersion } }),
+ });
+
+ // Act
+ const version = await globalService.getLatestVersion();
+
+ // Assert
+ expect(fetch).toHaveBeenCalledWith('https://registry.npmmirror.com/@lobehub/chat');
+ expect(version).toBe(mockVersion);
});
- // Act
- const version = await globalService.getLatestVersion();
+ it('should return undefined if the latest version is not found in the response', async () => {
+ // Arrange
+ (fetch as Mock).mockResolvedValue({
+ json: () => Promise.resolve({}),
+ });
- // Assert
- expect(fetch).toHaveBeenCalledWith('https://registry.npmmirror.com/@lobehub/chat');
- expect(version).toBe(mockVersion);
- });
+ // Act
+ const version = await globalService.getLatestVersion();
- it('should return undefined if the latest version is not found in the response', async () => {
- // Arrange
- (fetch as Mock).mockResolvedValue({
- json: () => Promise.resolve({}),
+ // Assert
+ expect(version).toBeUndefined();
});
- // Act
- const version = await globalService.getLatestVersion();
+ it('should throw an error when the fetch call fails', async () => {
+ // Arrange
+ (fetch as Mock).mockRejectedValue(new Error('Network error'));
- // Assert
- expect(version).toBeUndefined();
- });
+ // Act & Assert
+ await expect(globalService.getLatestVersion()).rejects.toThrow('Network error');
+ });
- it('should throw an error when the fetch call fails', async () => {
- // Arrange
- (fetch as Mock).mockRejectedValue(new Error('Network error'));
+ it('should handle non-JSON responses gracefully', async () => {
+ // Arrange
+ (fetch as Mock).mockResolvedValue({
+ json: () => Promise.reject(new SyntaxError('Unexpected token < in JSON at position 0')),
+ });
- // Act & Assert
- await expect(globalService.getLatestVersion()).rejects.toThrow('Network error');
+ // Act & Assert
+ await expect(globalService.getLatestVersion()).rejects.toThrow(SyntaxError);
+ });
});
- it('should handle non-JSON responses gracefully', async () => {
- // Arrange
- (fetch as Mock).mockResolvedValue({
- json: () => Promise.reject(new SyntaxError('Unexpected token < in JSON at position 0')),
- });
+ describe('ServerConfig', () => {
+ it('should return the serverConfig when fetch is successful', async () => {
+ // Arrange
+ (fetch as Mock).mockResolvedValue({
+ json: () => Promise.resolve({ customModelName: 'abc' }),
+ });
- // Act & Assert
- await expect(globalService.getLatestVersion()).rejects.toThrow(SyntaxError);
+ // Act
+ const config = await globalService.getGlobalConfig();
+
+ // Assert
+ expect(config).toEqual({ customModelName: 'abc' });
+ });
});
});
diff --git a/src/services/_url.ts b/src/services/_url.ts
index 8aad8796e47d..a827f89b2059 100644
--- a/src/services/_url.ts
+++ b/src/services/_url.ts
@@ -1,4 +1,5 @@
export const URLS = {
+ config: '/api/config',
market: '/api/market',
plugins: '/api/plugins',
};
diff --git a/src/services/agentMarket.ts b/src/services/agentMarket.ts
deleted file mode 100644
index f9e6d4271fdb..000000000000
--- a/src/services/agentMarket.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { URLS } from '@/services/_url';
-import { LobeChatAgentsMarketIndex } from '@/types/market';
-
-/**
- * 请求助手列表
- */
-export const getAgentList = async (locale: string): Promise => {
- const res = await fetch(`${URLS.market}?locale=${locale}`);
-
- return res.json();
-};
-
-/**
- * 请求助手 manifest
- */
-export const getAgentManifest = async (identifier: string, locale: string) => {
- if (!identifier) return;
- const res = await fetch(`${URLS.market}/${identifier}?locale=${locale}`);
-
- return res.json();
-};
diff --git a/src/services/global.ts b/src/services/global.ts
index 182f49a7899f..a661728c522f 100644
--- a/src/services/global.ts
+++ b/src/services/global.ts
@@ -1,3 +1,6 @@
+import { URLS } from '@/services/_url';
+import { GlobalServerConfig } from '@/types/settings';
+
const VERSION_URL = 'https://registry.npmmirror.com/@lobehub/chat';
class GlobalService {
@@ -10,6 +13,12 @@ class GlobalService {
return data['dist-tags']?.latest;
};
+
+ getGlobalConfig = async (): Promise => {
+ const res = await fetch(URLS.config);
+
+ return res.json();
+ };
}
export const globalService = new GlobalService();
diff --git a/src/services/market.ts b/src/services/market.ts
new file mode 100644
index 000000000000..5374767131b8
--- /dev/null
+++ b/src/services/market.ts
@@ -0,0 +1,21 @@
+import { URLS } from '@/services/_url';
+import { LobeChatAgentsMarketIndex } from '@/types/market';
+
+class MarketService {
+ getAgentList = async (locale: string): Promise => {
+ const res = await fetch(`${URLS.market}?locale=${locale}`);
+
+ return res.json();
+ };
+
+ /**
+ * 请求助手 manifest
+ */
+ getAgentManifest = async (identifier: string, locale: string) => {
+ if (!identifier) return;
+ const res = await fetch(`${URLS.market}/${identifier}?locale=${locale}`);
+
+ return res.json();
+ };
+}
+export const marketService = new MarketService();
diff --git a/src/store/global/initialState.ts b/src/store/global/initialState.ts
index 136510f30ad8..0ab951914b07 100644
--- a/src/store/global/initialState.ts
+++ b/src/store/global/initialState.ts
@@ -1,5 +1,5 @@
import { DEFAULT_SETTINGS } from '@/const/settings';
-import type { GlobalSettings } from '@/types/settings';
+import type { GlobalServerConfig, GlobalSettings } from '@/types/settings';
export enum SidebarTabKey {
Chat = 'chat',
@@ -38,6 +38,7 @@ export interface GlobalState {
* @localStorage
*/
preference: GlobalPreference;
+ serverConfig: GlobalServerConfig;
/**
* @localStorage
* 用户设置
@@ -58,6 +59,7 @@ export const initialState: GlobalState = {
showSessionPanel: true,
showSystemRole: false,
},
+ serverConfig: {},
settings: DEFAULT_SETTINGS,
settingsTab: SettingsTabs.Common,
sidebarKey: SidebarTabKey.Chat,
diff --git a/src/store/global/selectors/__snapshots__/settings.test.ts.snap b/src/store/global/selectors/__snapshots__/settings.test.ts.snap
index 6ab0d61aaece..5c50b28de2f0 100644
--- a/src/store/global/selectors/__snapshots__/settings.test.ts.snap
+++ b/src/store/global/selectors/__snapshots__/settings.test.ts.snap
@@ -136,7 +136,6 @@ exports[`settingsSelectors > currentSettings > should merge DEFAULT_SETTINGS and
"languageModel": {
"openAI": {
"OPENAI_API_KEY": "openai-api-key",
- "customModelName": undefined,
"endpoint": "https://openai-endpoint.com",
"models": [
"gpt-3.5-turbo",
diff --git a/src/store/global/selectors/settings.test.ts b/src/store/global/selectors/settings.test.ts
index fb9aba4d40a1..079ce97a1a85 100644
--- a/src/store/global/selectors/settings.test.ts
+++ b/src/store/global/selectors/settings.test.ts
@@ -61,12 +61,13 @@ describe('settingsSelectors', () => {
describe('CUSTOM_MODELS', () => {
it('custom deletion, addition, and renaming of models', () => {
const s = {
+ serverConfig: {
+ customModelName:
+ '-all,+llama,+claude-2,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo,gpt-4-1106-preview=gpt-4-32k',
+ },
settings: {
languageModel: {
- openAI: {
- customModelName:
- '-all,+llama,+claude-2,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo,gpt-4-1106-preview=gpt-4-32k',
- },
+ openAI: {},
},
},
} as unknown as GlobalStore;
@@ -78,6 +79,7 @@ describe('settingsSelectors', () => {
it('duplicate naming model', () => {
const s = {
+ serverConfig: {},
settings: {
languageModel: {
openAI: {
@@ -94,6 +96,7 @@ describe('settingsSelectors', () => {
it('only add the model', () => {
const s = {
+ serverConfig: {},
settings: {
languageModel: {
openAI: {
diff --git a/src/store/global/selectors/settings.ts b/src/store/global/selectors/settings.ts
index 3979a25085d6..4cd939664300 100644
--- a/src/store/global/selectors/settings.ts
+++ b/src/store/global/selectors/settings.ts
@@ -34,6 +34,7 @@ const modelListSelectors = (s: GlobalStore) => {
const modelNames = [
...DEFAULT_OPENAI_MODEL_LIST,
+ ...(s.serverConfig.customModelName || '').split(/[,,]/).filter(Boolean),
...(s.settings.languageModel.openAI.customModelName || '').split(/[,,]/).filter(Boolean),
];
diff --git a/src/store/global/slices/common.ts b/src/store/global/slices/common.ts
index 309d64bfe4fa..d0c1b6fb788e 100644
--- a/src/store/global/slices/common.ts
+++ b/src/store/global/slices/common.ts
@@ -5,6 +5,7 @@ import type { StateCreator } from 'zustand/vanilla';
import { CURRENT_VERSION } from '@/const/version';
import { globalService } from '@/services/global';
+import type { GlobalServerConfig } from '@/types/settings';
import { merge } from '@/utils/merge';
import { setNamespace } from '@/utils/storeDebug';
@@ -28,6 +29,7 @@ export interface CommonAction {
updateGuideState: (guide: Partial) => void;
updatePreference: (preference: Partial, action?: string) => void;
useCheckLatestVersion: () => SWRResponse;
+ useFetchGlobalConfig: () => SWRResponse;
}
export const createCommonSlice: StateCreator<
@@ -78,4 +80,11 @@ export const createCommonSlice: StateCreator<
set({ hasNewVersion: true, latestVersion: data }, false, n('checkLatestVersion'));
},
}),
+ useFetchGlobalConfig: () =>
+ useSWR('fetchGlobalConfig', globalService.getGlobalConfig, {
+ onSuccess: (data) => {
+ if (data) set({ serverConfig: data });
+ },
+ revalidateOnFocus: false,
+ }),
});
diff --git a/src/store/market/action.ts b/src/store/market/action.ts
index 4e5a55dbaee2..3ced8e9947fb 100644
--- a/src/store/market/action.ts
+++ b/src/store/market/action.ts
@@ -3,7 +3,7 @@ import { produce } from 'immer';
import useSWR, { SWRResponse } from 'swr';
import type { StateCreator } from 'zustand/vanilla';
-import { getAgentList, getAgentManifest } from '@/services/agentMarket';
+import { marketService } from '@/services/market';
import { globalHelpers } from '@/store/global/helpers';
import { AgentsMarketItem, LobeChatAgentsMarketIndex } from '@/types/market';
@@ -47,7 +47,7 @@ export const createMarketAction: StateCreator<
useFetchAgent: (identifier) =>
useSWR(
[identifier, globalHelpers.getCurrentLanguage()],
- ([id, locale]) => getAgentManifest(id, locale as string),
+ ([id, locale]) => marketService.getAgentManifest(id, locale as string),
{
onError: () => {
get().deactivateAgent();
@@ -58,13 +58,17 @@ export const createMarketAction: StateCreator<
},
),
useFetchAgentList: () =>
- useSWR(globalHelpers.getCurrentLanguage(), getAgentList, {
- onSuccess: (agentMarketIndex) => {
- set(
- { agentList: agentMarketIndex.agents, tagList: agentMarketIndex.tags },
- false,
- 'useFetchAgentList',
- );
+ useSWR(
+ globalHelpers.getCurrentLanguage(),
+ marketService.getAgentList,
+ {
+ onSuccess: (agentMarketIndex) => {
+ set(
+ { agentList: agentMarketIndex.agents, tagList: agentMarketIndex.tags },
+ false,
+ 'useFetchAgentList',
+ );
+ },
},
- }),
+ ),
});
diff --git a/src/types/settings.ts b/src/types/settings.ts
index 8b3b469743fc..771c746b3888 100644
--- a/src/types/settings.ts
+++ b/src/types/settings.ts
@@ -91,3 +91,7 @@ export interface GlobalSettings extends GlobalBaseSettings {
}
export type ConfigKeys = keyof GlobalSettings;
+
+export interface GlobalServerConfig {
+ customModelName?: string;
+}