Skip to content

Commit

Permalink
🐛 fix: add basePath to support subdirectory (lobehub#1179)
Browse files Browse the repository at this point in the history
* 🔧 chore: test the base path

* 🐛 fix: fix api base path

* 🐛 fix: fix api base path

* 🐛 fix: fix api base path

* 🐛 fix: fix image base path

* 🐛 fix: fix api base path

* 🐛 fix: fix image base path

* 🐛 fix: fix manifest.json

* 🐛 fix: fix manifest.json

* ✅ test: fix test

* 🔧 chore: clean code

* 🔧 chore: clean code

* 📝 docs: update docs

* 🐛 fix: fix api base path

* ✅ test: fix test

* 🐛 fix: fix image base path

* 🐛 fix: fix image base path
  • Loading branch information
arvinxx authored Feb 9, 2024
1 parent 105e1a8 commit 43e544a
Show file tree
Hide file tree
Showing 22 changed files with 103 additions and 46 deletions.
10 changes: 9 additions & 1 deletion docs/Deployment/Environment-Variable.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ LobeChat provides additional configuration options during deployment, which can
- [General Variables](#general-variables)
- [`ACCESS_CODE`](#access_code)
- [`ENABLE_OAUTH_SSO`](#enable_oauth_sso)
- [`NEXT_PUBLIC_BASE_PATH`](#next_public_base_path)
- [Authentication Service Providers](#authentication-service-providers)
- [Common Settings](#common-settings)
- [Auth0](#auth0)
Expand Down Expand Up @@ -41,7 +42,14 @@ LobeChat provides additional configuration options during deployment, which can
- Type: Optional
- Description: Enable OAuth single sign-on (SSO) for LobeChat. Set to `1` to enable OAuth SSO. See [Authentication Service Providers](#authentication-service-providers) for more details.
- Default: `-`
- Example: `1` or `0`
- Example: `1`

### `NEXT_PUBLIC_BASE_PATH`

- Type:Optional
- Description:add `basePath` for LobeChat
- Default: `-`
- Example: `/test`

## Authentication Service Providers

Expand Down
10 changes: 9 additions & 1 deletion docs/Deployment/Environment-Variable.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
- [通用变量](#通用变量)
- [`ACCESS_CODE`](#access_code)
- [`ENABLE_OAUTH_SSO`](#enable_oauth_sso)
- [`NEXT_PUBLIC_BASE_PATH`](#next_public_base_path)
- [身份验证服务](#身份验证服务)
- [通用设置](#通用设置)
- [Auth0](#auth0)
Expand Down Expand Up @@ -41,7 +42,14 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
- 类型:可选
- 描述:为 LobeChat 启用单点登录 (SSO)。设置为 `1` 以启用单点登录。有关详细信息,请参阅[身份验证服务](#身份验证服务)
- 默认值: `-`
- 示例: `1``0`
- 示例: `1`

### `NEXT_PUBLIC_BASE_PATH`

- 类型:可选
- 描述:为 LobeChat 添加 `basePath`
- 默认值: `-`
- 示例: `/test`

## 身份验证服务

Expand Down
3 changes: 3 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const buildWithDocker = process.env.DOCKER === 'true';
// if you need to proxy the api endpoint to remote server
const API_PROXY_ENDPOINT = process.env.API_PROXY_ENDPOINT || '';

const basePath = process.env.NEXT_PUBLIC_BASE_PATH;

const withBundleAnalyzer = analyzer({
enabled: process.env.ANALYZE === 'true',
});
Expand All @@ -22,6 +24,7 @@ const withPWA = nextPWA({
/** @type {import('next').NextConfig} */
const nextConfig = {
compress: isProd,
basePath,
experimental: {
optimizePackageImports: [
'emoji-mart',
Expand Down
4 changes: 3 additions & 1 deletion src/app/chat/features/ChatHeader/ShareButton/style.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { createStyles } from 'antd-style';

import { imageUrl } from '@/const/url';

export const useStyles = createStyles(({ css, token, stylish, cx }, withBackground: boolean) => ({
background: css`
padding: 24px;
background-color: ${token.colorBgLayout};
background-image: url('/images/screenshot_background.webp');
background-image: url(${imageUrl('screenshot_background.webp')});
background-position: center;
background-size: 120% 120%;
`,
Expand Down
3 changes: 2 additions & 1 deletion src/app/chat/features/TopicListContent/Topic/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';

import { imageUrl } from '@/const/url';
import { useChatStore } from '@/store/chat';
import { topicSelectors } from '@/store/chat/selectors';
import { useGlobalStore } from '@/store/global';
Expand Down Expand Up @@ -60,7 +61,7 @@ export const Topic = memo(() => {
<Flexbox flex={1}>
<EmptyCard
alt={t('topic.guide.desc')}
cover={`/images/empty_topic_${isDarkMode ? 'dark' : 'light'}.webp`}
cover={imageUrl(`empty_topic_${isDarkMode ? 'dark' : 'light'}.webp`)}
desc={t('topic.guide.desc')}
height={120}
onVisibleChange={(visible) => {
Expand Down
4 changes: 2 additions & 2 deletions src/app/market/features/ShareAgentButton/Inner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Image from 'next/image';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';

import { AGENTS_INDEX_GITHUB } from '@/const/url';
import { AGENTS_INDEX_GITHUB, imageUrl } from '@/const/url';

const Inner = memo(() => {
const { t } = useTranslation('market');
Expand All @@ -14,7 +14,7 @@ const Inner = memo(() => {
<Image
alt={'banner'}
height={602}
src={'/images/banner_market_modal.webp'}
src={imageUrl('banner_market_modal.webp')}
style={{ height: 'auto', marginBottom: 24, width: '100%' }}
width={1602}
/>
Expand Down
7 changes: 6 additions & 1 deletion src/app/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Metadata } from 'next';

import { getClientConfig } from '@/config/client';
import { getServerConfig } from '@/config/server';

import pkg from '../../package.json';
Expand All @@ -8,6 +9,10 @@ const title = 'LobeChat';
const { description, homepage } = pkg;

const { METADATA_BASE_URL = 'https://chat-preview.lobehub.com/' } = getServerConfig();
const { BASE_PATH } = getClientConfig();

// if there is a base path, then we don't need the manifest
const noManifest = !!BASE_PATH;

const metadata: Metadata = {
appleWebApp: {
Expand All @@ -22,7 +27,7 @@ const metadata: Metadata = {
shortcut:
'https://registry.npmmirror.com/@lobehub/assets-favicons/latest/files/assets/favicon.ico',
},
manifest: '/manifest.json',
manifest: noManifest ? undefined : '/manifest.json',
metadataBase: new URL(METADATA_BASE_URL),
openGraph: {
description: description,
Expand Down
7 changes: 4 additions & 3 deletions src/app/settings/common/Common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useTranslation } from 'react-i18next';

import { FORM_STYLE } from '@/const/layoutTokens';
import { DEFAULT_SETTINGS } from '@/const/settings';
import { imageUrl } from '@/const/url';
import AvatarWithUpload from '@/features/AvatarWithUpload';
import { localeOptions } from '@/locales/resources';
import { useChatStore } from '@/store/chat';
Expand Down Expand Up @@ -118,19 +119,19 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig, showOAuthLogin
options={[
{
icon: Sun,
img: '/images/theme_light.webp',
img: imageUrl('theme_light.webp'),
label: t('settingTheme.themeMode.light'),
value: 'light',
},
{
icon: Moon,
img: '/images/theme_dark.webp',
img: imageUrl('theme_dark.webp'),
label: t('settingTheme.themeMode.dark'),
value: 'dark',
},
{
icon: Monitor,
img: '/images/theme_auto.webp',
img: imageUrl('theme_auto.webp'),
label: t('settingTheme.themeMode.auto'),
value: 'auto',
},
Expand Down
1 change: 1 addition & 0 deletions src/config/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ declare global {
}

export const getClientConfig = () => ({
BASE_PATH: process.env.NEXT_PUBLIC_BASE_PATH || '',
// Vercel Analytics
ANALYTICS_VERCEL: process.env.NEXT_PUBLIC_ANALYTICS_VERCEL === '1',
VERCEL_DEBUG: process.env.NEXT_PUBLIC_VERCEL_DEBUG === '1',
Expand Down
4 changes: 4 additions & 0 deletions src/const/url.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import urlJoin from 'url-join';

import { withBasePath } from '@/utils/basePath';

import pkg from '../../package.json';
import { INBOX_SESSION_ID } from './session';

Expand All @@ -19,3 +21,5 @@ export const AGENTS_INDEX_GITHUB_ISSUE = urlJoin(AGENTS_INDEX_GITHUB, 'issues/ne
export const SESSION_CHAT_URL = (id: string = INBOX_SESSION_ID, mobile?: boolean) =>
mobile ? `/chat/mobile?session=${id}` : `/chat?session=${id}`;
export const MANUAL_UPGRADE_URL = 'https://github.com/lobehub/lobe-chat/wiki/Upstream-Sync';

export const imageUrl = (filename: string) => withBasePath(`/images/${filename}`);
5 changes: 3 additions & 2 deletions src/features/AgentSetting/AgentConfig/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';

import { FORM_STYLE } from '@/const/layoutTokens';
import { imageUrl } from '@/const/url';

import { useStore } from '../store';
import ModelSelect from './ModelSelect';
Expand Down Expand Up @@ -46,13 +47,13 @@ const AgentConfig = memo(() => {
options={[
{
icon: MessagesSquare,
img: `/images/chatmode_chat_${isDarkMode ? 'dark' : 'light'}.webp`,
img: imageUrl(`chatmode_chat_${isDarkMode ? 'dark' : 'light'}.webp`),
label: t('settingChat.chatStyleType.type.chat'),
value: 'chat',
},
{
icon: LayoutList,
img: `/images/chatmode_docs_${isDarkMode ? 'dark' : 'light'}.webp`,
img: imageUrl(`chatmode_docs_${isDarkMode ? 'dark' : 'light'}.webp`),
label: t('settingChat.chatStyleType.type.docs'),
value: 'docs',
},
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useSTT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import isEqual from 'fast-deep-equal';
import { SWRConfiguration } from 'swr';

import { createHeaderWithOpenAI } from '@/services/_header';
import { OPENAI_URLS } from '@/services/_url';
import { API_ENDPOINTS } from '@/services/_url';
import { useGlobalStore } from '@/store/global';
import { settingsSelectors } from '@/store/global/selectors';
import { useSessionStore } from '@/store/session';
Expand Down Expand Up @@ -39,7 +39,7 @@ export const useSTT = (config: STTConfig) => {
options = {
api: {
headers: createHeaderWithOpenAI(),
serviceUrl: OPENAI_URLS.stt,
serviceUrl: API_ENDPOINTS.stt,
},
autoStop,
options: {
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useTTS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import isEqual from 'fast-deep-equal';

import { createHeaderWithOpenAI } from '@/services/_header';
import { OPENAI_URLS, TTS_URL } from '@/services/_url';
import { API_ENDPOINTS } from '@/services/_url';
import { useGlobalStore } from '@/store/global';
import { settingsSelectors } from '@/store/global/selectors';
import { useSessionStore } from '@/store/session';
Expand All @@ -36,7 +36,7 @@ export const useTTS = (content: string, config?: TTSConfig) => {
options = {
api: {
headers: createHeaderWithOpenAI(),
serviceUrl: OPENAI_URLS.tts,
serviceUrl: API_ENDPOINTS.tts,
},
options: {
model: ttsSettings.openAI.ttsModel,
Expand Down Expand Up @@ -64,7 +64,7 @@ export const useTTS = (content: string, config?: TTSConfig) => {
useSelectedTTS = useMicrosoftSpeech;
options = {
api: {
serviceUrl: TTS_URL.microsoft,
serviceUrl: API_ENDPOINTS.microsoft,
},
options: {
voice: config?.voice || voice,
Expand Down
40 changes: 29 additions & 11 deletions src/services/_url.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
export const URLS = {
/* eslint-disable sort-keys-fix/sort-keys-fix */
import { transform } from 'lodash-es';

import { withBasePath } from '@/utils/basePath';

const mapWithBasePath = <T extends object>(apis: T): T => {
return transform(apis, (result, value, key) => {
if (typeof value === 'string') {
// @ts-ignore
result[key] = withBasePath(value);
} else {
result[key] = value;
}
});
};

export const API_ENDPOINTS = mapWithBasePath({
config: '/api/config',
market: '/api/market',
proxy: '/api/proxy',
};

export const PLUGINS_URLS = {
// agent markets
market: '/api/market',

// plugins
gateway: '/api/plugin/gateway',
store: '/api/plugin/store',
};
pluginStore: '/api/plugin/store',

export const OPENAI_URLS = {
// chat
chat: (provider: string) => withBasePath(`/api/chat/${provider}`),

// image
images: '/api/openai/images',

// TTS & STT
stt: '/api/openai/stt',
tts: '/api/openai/tts',
};

export const TTS_URL = {
edge: '/api/tts/edge-speech',
microsoft: '/api/tts/microsoft-speech',
};
});
6 changes: 3 additions & 3 deletions src/services/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { fetchAIFactory, getMessageError } from '@/utils/fetch';

import { createHeaderWithAuth } from './_auth';
import { createHeaderWithOpenAI } from './_header';
import { PLUGINS_URLS } from './_url';
import { API_ENDPOINTS } from './_url';

interface FetchOptions {
signal?: AbortSignal | undefined;
Expand Down Expand Up @@ -81,7 +81,7 @@ class ChatService {
provider,
});

return fetch(`/api/chat/${provider}`, {
return fetch(API_ENDPOINTS.chat(provider), {
body: JSON.stringify(payload),
headers,
method: 'POST',
Expand All @@ -102,7 +102,7 @@ class ChatService {

const gatewayURL = manifest?.gateway;

const res = await fetch(gatewayURL ?? PLUGINS_URLS.gateway, {
const res = await fetch(gatewayURL ?? API_ENDPOINTS.gateway, {
body: JSON.stringify({ ...params, manifest }),
// TODO: we can have a better auth way
headers: createHeadersWithPluginSettings(settings, createHeaderWithOpenAI()),
Expand Down
5 changes: 3 additions & 2 deletions src/services/file.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { FileModel } from '@/database/models/file';
import { DB_File } from '@/database/schemas/files';
import { URLS } from '@/services/_url';
import { FilePreview } from '@/types/files';
import compressImage from '@/utils/compressImage';

import { API_ENDPOINTS } from './_url';

class FileService {
private isImage(fileType: string) {
const imageRegex = /^image\//;
Expand Down Expand Up @@ -43,7 +44,7 @@ class FileService {
}

async uploadImageByUrl(url: string, file: Pick<DB_File, 'name' | 'metadata'>) {
const res = await fetch(URLS.proxy, { body: url, method: 'POST' });
const res = await fetch(API_ENDPOINTS.proxy, { body: url, method: 'POST' });
const data = await res.arrayBuffer();
const fileType = res.headers.get('content-type') || 'image/webp';

Expand Down
5 changes: 3 additions & 2 deletions src/services/global.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { URLS } from '@/services/_url';
import { GlobalServerConfig } from '@/types/settings';

import { API_ENDPOINTS } from './_url';

const VERSION_URL = 'https://registry.npmmirror.com/@lobehub/chat';

class GlobalService {
Expand All @@ -15,7 +16,7 @@ class GlobalService {
};

getGlobalConfig = async (): Promise<GlobalServerConfig> => {
const res = await fetch(URLS.config);
const res = await fetch(API_ENDPOINTS.config);

return res.json();
};
Expand Down
5 changes: 3 additions & 2 deletions src/services/imageGeneration.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createHeaderWithOpenAI } from '@/services/_header';
import { OPENAI_URLS } from '@/services/_url';
import { OpenAIImagePayload } from '@/types/openai/image';

import { API_ENDPOINTS } from './_url';

interface FetchOptions {
signal?: AbortSignal | undefined;
}
Expand All @@ -10,7 +11,7 @@ class ImageGenerationService {
async generateImage(params: Omit<OpenAIImagePayload, 'model' | 'n'>, options?: FetchOptions) {
const payload: OpenAIImagePayload = { ...params, model: 'dall-e-3', n: 1 };

const res = await fetch(OPENAI_URLS.images, {
const res = await fetch(API_ENDPOINTS.images, {
body: JSON.stringify(payload),
headers: createHeaderWithOpenAI({ 'Content-Type': 'application/json' }),
method: 'POST',
Expand Down
Loading

0 comments on commit 43e544a

Please sign in to comment.