@lobehub/chat 0.82.6 → 0.82.8

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 (43) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/docs/Environment-Variable.md +0 -13
  3. package/docs/Environment-Variable.zh-CN.md +0 -11
  4. package/next.config.mjs +6 -19
  5. package/package.json +5 -1
  6. package/src/{pages/api/openai/chat.api.ts → app/api/openai/chat/route.ts} +6 -6
  7. package/src/{pages/api → app/api/openai}/createChatCompletion.ts +1 -1
  8. package/src/{pages/api/openai/models.api.ts → app/api/openai/models/route.ts} +3 -3
  9. package/src/app/api/plugins/route.ts +7 -0
  10. package/src/app/chat/features/Conversation/Input/ActionBar/Token.tsx +10 -7
  11. package/src/app/chat/features/Conversation/Input/index.tsx +7 -3
  12. package/src/app/home/Redirect.tsx +29 -0
  13. package/src/app/home/index.tsx +18 -29
  14. package/src/app/market/features/SideBar/AgentInfo/TokenTag.tsx +4 -3
  15. package/src/app/page.tsx +7 -3
  16. package/src/app/welcome/{layout.desktop.tsx → DesktopLayout/Desktop.tsx} +5 -4
  17. package/src/app/welcome/DesktopLayout/index.tsx +15 -0
  18. package/src/app/welcome/{layout.mobile.tsx → MobileLayout.tsx} +6 -3
  19. package/src/app/welcome/features/Banner/index.tsx +2 -0
  20. package/src/app/welcome/features/Footer/index.tsx +2 -0
  21. package/src/app/welcome/layout.tsx +22 -0
  22. package/src/app/welcome/page.tsx +13 -4
  23. package/src/features/AgentSetting/AgentPrompt/index.tsx +3 -6
  24. package/src/hooks/useTokenCount.ts +20 -0
  25. package/src/prompts/chat.ts +4 -2
  26. package/src/services/_url.ts +3 -12
  27. package/src/store/session/hooks/useEffectAfterHydrated.ts +11 -6
  28. package/src/store/session/slices/chat/actions/message.ts +1 -1
  29. package/src/store/session/slices/chat/actions/topic.ts +3 -3
  30. package/src/store/session/slices/chat/helpers.ts +3 -4
  31. package/src/store/session/slices/chat/selectors/chat.ts +5 -0
  32. package/src/store/session/slices/chat/selectors/index.ts +9 -5
  33. package/src/store/session/slices/session/action.ts +1 -1
  34. package/src/utils/responsive.ts +18 -0
  35. package/src/utils/tokenizer.ts +5 -0
  36. package/src/app/home/Loading.tsx +0 -23
  37. package/src/app/welcome/index.tsx +0 -39
  38. package/src/pages/api/plugins.api.ts +0 -9
  39. package/src/store/session/slices/chat/selectors/token.ts +0 -28
  40. /package/src/{pages → app}/api/auth.ts +0 -0
  41. /package/src/{pages → app}/api/openai/createAzureOpenai.ts +0 -0
  42. /package/src/{pages → app}/api/openai/createOpenai.ts +0 -0
  43. /package/src/{pages/api/error.ts → app/api/openai/errorResponse.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.82.8](https://github.com/lobehub/lobe-chat/compare/v0.82.7...v0.82.8)
6
+
7
+ <sup>Released on **2023-09-30**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor / route to reduce page js size.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor / route to reduce page js size ([79f0347](https://github.com/lobehub/lobe-chat/commit/79f0347))
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.82.7](https://github.com/lobehub/lobe-chat/compare/v0.82.6...v0.82.7)
31
+
32
+ <sup>Released on **2023-09-30**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Refactor the api router to app route handlers.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **misc**: Refactor the api router to app route handlers, closes [#254](https://github.com/lobehub/lobe-chat/issues/254) ([f032112](https://github.com/lobehub/lobe-chat/commit/f032112))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 0.82.6](https://github.com/lobehub/lobe-chat/compare/v0.82.5...v0.82.6)
6
56
 
7
57
  <sup>Released on **2023-09-29**</sup>
@@ -19,8 +19,6 @@ LobeChat provides additional configuration options during deployment, which can
19
19
  - [`AGENTS_INDEX_URL`](#agents_index_url)
20
20
  - [Data Analytics](#data-analytics)
21
21
  - [Posthog Analytics](#posthog-analytics)
22
- - [Development Environment](#development-environment)
23
- - [`DEV_API_END_PORT_URL`](#dev_api_end_port_url)
24
22
 
25
23
  ## General Variables
26
24
 
@@ -130,16 +128,5 @@ If you need to use Azure OpenAI to provide model services, you can refer to the
130
128
  - Default: -
131
129
  - Example: `1`
132
130
 
133
- <br/>
134
-
135
- ## Development Environment
136
-
137
- ### `DEV_API_END_PORT_URL`
138
-
139
- - Type: Optional
140
- - Description: Define the proxy address of the LobeChat server request forwarding. Using this variable can conveniently forward requests to the line during development. See [configuration code](https://github.com/lobehub/lobe-chat/blob/main/next.config.mjs#L29-L38)
141
- - Default: -
142
- - Example: `https://chat-preview.lobehub.com`
143
-
144
131
  [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
145
132
  [openai-api-page]: https://platform.openai.com/account/api-keys
@@ -21,8 +21,6 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
21
21
  - [Vercel Analytics](#vercel-analytics)
22
22
  - [Mixpanel Analytics](#mixpanel-analytics)
23
23
  - [Posthog Analytics](#posthog-analytics)
24
- - [开发环境](#开发环境)
25
- - [`DEV_API_END_PORT_URL`](#dev_api_end_port_url)
26
24
 
27
25
  ## 通用变量
28
26
 
@@ -169,15 +167,6 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
169
167
  - 默认值: -
170
168
  - 示例:`1`
171
169
 
172
- ## 开发环境
173
-
174
- ### `DEV_API_END_PORT_URL`
175
-
176
- - 类型:可选
177
- - 描述:定义 LobeChat 服务端请求转发的代理地址,使用该变量可以方便开发时将请求转发到线上。详见[配置代码](https://github.com/lobehub/lobe-chat/blob/main/next.config.mjs#L29-L38)
178
- - 默认值:-
179
- - 示例:`https://chat-preview.lobehub.com`
180
-
181
170
  [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
182
171
  [mixpanel-analytics-url]: https://mixpanel.com
183
172
  [mixpanel-project-url]: https://mixpanel.com/settings/project
package/next.config.mjs CHANGED
@@ -1,7 +1,11 @@
1
+ import analyzer from '@next/bundle-analyzer';
1
2
  import nextPWA from 'next-pwa';
2
3
 
3
4
  const isProd = process.env.NODE_ENV === 'production';
4
- const DEV_API_END_PORT_URL = process.env.DEV_API_END_PORT_URL || '';
5
+
6
+ const withBundleAnalyzer = analyzer({
7
+ enabled: process.env.ANALYZE === 'true',
8
+ });
5
9
 
6
10
  const withPWA = nextPWA({
7
11
  dest: 'public',
@@ -12,7 +16,6 @@ const withPWA = nextPWA({
12
16
  /** @type {import('next').NextConfig} */
13
17
  const nextConfig = {
14
18
  reactStrictMode: true,
15
- pageExtensions: ['tsx', 'api.ts'],
16
19
  // not sure why antd-style cause multi ThemeProvider instance
17
20
  // So we need to transpile it to lib mode
18
21
  transpilePackages: ['@lobehub/ui', 'antd-style'],
@@ -25,26 +28,10 @@ const nextConfig = {
25
28
  return config;
26
29
  },
27
30
 
28
- async rewrites() {
29
- return [
30
- {
31
- source: '/api/openai/chat-dev',
32
- destination: `${DEV_API_END_PORT_URL}/api/openai/chat`,
33
- },
34
- {
35
- source: '/api/openai/models-dev',
36
- destination: `${DEV_API_END_PORT_URL}/api/openai/models`,
37
- },
38
- {
39
- source: '/api/plugins-dev',
40
- destination: `${DEV_API_END_PORT_URL}/api/plugins`,
41
- },
42
- ];
43
- },
44
31
  env: {
45
32
  AGENTS_INDEX_URL: process.env.AGENTS_INDEX_URL,
46
33
  PLUGINS_INDEX_URL: process.env.PLUGINS_INDEX_URL,
47
34
  },
48
35
  };
49
36
 
50
- export default isProd ? withPWA(nextConfig) : nextConfig;
37
+ export default isProd ? withBundleAnalyzer(withPWA(nextConfig)) : nextConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.82.6",
3
+ "version": "0.82.8",
4
4
  "description": "An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -24,6 +24,7 @@
24
24
  "sideEffects": false,
25
25
  "scripts": {
26
26
  "build": "next build",
27
+ "build:analyze": "ANALYZE=true next build",
27
28
  "dev": "next dev -p 3010",
28
29
  "i18n": "npm run i18n:workflow && lobe-i18n",
29
30
  "i18n:workflow": "ts-node --project ./tsconfig.json scripts/i18nWorkflow/index.ts",
@@ -106,6 +107,7 @@
106
107
  "swr": "^2",
107
108
  "systemjs": "^6",
108
109
  "ts-md5": "^1",
110
+ "ua-parser-js": "^1",
109
111
  "url-join": "^5",
110
112
  "use-merge-value": "^1",
111
113
  "utility-types": "^3",
@@ -117,6 +119,7 @@
117
119
  "@commitlint/cli": "^17",
118
120
  "@lobehub/i18n-cli": "latest",
119
121
  "@lobehub/lint": "latest",
122
+ "@next/bundle-analyzer": "^13",
120
123
  "@next/eslint-plugin-next": "^13",
121
124
  "@testing-library/jest-dom": "^6",
122
125
  "@testing-library/react": "^14",
@@ -127,6 +130,7 @@
127
130
  "@types/react": "^18",
128
131
  "@types/react-dom": "^18",
129
132
  "@types/systemjs": "^6",
133
+ "@types/ua-parser-js": "^0.7",
130
134
  "@types/uuid": "^9",
131
135
  "@umijs/lint": "^4",
132
136
  "@vitest/coverage-v8": "latest",
@@ -5,15 +5,15 @@ import { getOpenAIAuthFromRequest } from '@/const/fetch';
5
5
  import { ErrorType } from '@/types/fetch';
6
6
  import { OpenAIStreamPayload } from '@/types/openai';
7
7
 
8
- import { checkAuth } from '../auth';
8
+ import { checkAuth } from '../../auth';
9
+ import { createAzureOpenai } from '../createAzureOpenai';
9
10
  import { createChatCompletion } from '../createChatCompletion';
10
- import { createErrorResponse } from '../error';
11
- import { createAzureOpenai } from './createAzureOpenai';
12
- import { createOpenai } from './createOpenai';
11
+ import { createOpenai } from '../createOpenai';
12
+ import { createErrorResponse } from '../errorResponse';
13
13
 
14
14
  export const runtime = 'edge';
15
15
 
16
- export default async function handler(req: Request) {
16
+ export const POST = async (req: Request) => {
17
17
  const payload = (await req.json()) as OpenAIStreamPayload;
18
18
 
19
19
  const { apiKey, accessCode, endpoint, useAzure, apiVersion } = getOpenAIAuthFromRequest(req);
@@ -36,4 +36,4 @@ export default async function handler(req: Request) {
36
36
  }
37
37
 
38
38
  return createChatCompletion({ openai, payload });
39
- }
39
+ };
@@ -1,7 +1,7 @@
1
1
  import { OpenAIStream, StreamingTextResponse } from 'ai';
2
2
  import OpenAI from 'openai';
3
3
 
4
- import { createErrorResponse } from '@/pages/api/error';
4
+ import { createErrorResponse } from '@/app/api/openai/errorResponse';
5
5
  import { ChatErrorType } from '@/types/fetch';
6
6
  import { OpenAIStreamPayload } from '@/types/openai';
7
7
 
@@ -1,10 +1,10 @@
1
1
  import { getOpenAIAuthFromRequest } from '@/const/fetch';
2
2
 
3
- import { createOpenai } from './createOpenai';
3
+ import { createOpenai } from '../createOpenai';
4
4
 
5
5
  export const runtime = 'edge';
6
6
 
7
- export default async function handler(req: Request) {
7
+ export const POST = async (req: Request) => {
8
8
  const { apiKey, endpoint } = getOpenAIAuthFromRequest(req);
9
9
 
10
10
  const openAI = createOpenai(apiKey, endpoint);
@@ -14,4 +14,4 @@ export default async function handler(req: Request) {
14
14
  const modelList = res.data.map((i) => i.id);
15
15
 
16
16
  return new Response(JSON.stringify(modelList));
17
- }
17
+ };
@@ -0,0 +1,7 @@
1
+ import { createLobeChatPluginGateway } from '@lobehub/chat-plugins-gateway';
2
+
3
+ import { PLUGINS_INDEX_URL } from '@/const/url';
4
+
5
+ export const runtime = 'edge';
6
+
7
+ export const POST = createLobeChatPluginGateway({ pluginsIndexUrl: PLUGINS_INDEX_URL });
@@ -1,24 +1,27 @@
1
1
  import { TokenTag, Tooltip } from '@lobehub/ui';
2
- import { encode } from 'gpt-tokenizer';
3
- import { memo, useMemo } from 'react';
2
+ import { memo } from 'react';
4
3
  import { useTranslation } from 'react-i18next';
5
4
 
6
5
  import { ModelTokens } from '@/const/modelTokens';
6
+ import { useTokenCount } from '@/hooks/useTokenCount';
7
7
  import { useSessionStore } from '@/store/session';
8
8
  import { agentSelectors, chatSelectors } from '@/store/session/selectors';
9
9
 
10
10
  const Token = memo<{ input: string }>(({ input }) => {
11
11
  const { t } = useTranslation('common');
12
12
 
13
- const inputTokenCount = useMemo(() => encode(input).length, [input]);
13
+ const inputTokenCount = useTokenCount(input);
14
14
 
15
- const [totalToken, systemRoleToken, chatsToken, model] = useSessionStore((s) => [
16
- chatSelectors.totalTokenCount(s),
17
- chatSelectors.systemRoleTokenCount(s),
18
- chatSelectors.chatsTokenCount(s),
15
+ const [messageString, systemRole, model] = useSessionStore((s) => [
16
+ chatSelectors.chatsMessageString(s),
17
+ agentSelectors.currentAgentSystemRole(s),
19
18
  agentSelectors.currentAgentModel(s),
20
19
  ]);
21
20
 
21
+ const systemRoleToken = useTokenCount(systemRole);
22
+ const chatsToken = useTokenCount(messageString);
23
+
24
+ const totalToken = systemRoleToken + chatsToken;
22
25
  return (
23
26
  <Tooltip placement={'bottom'} title={t('tokenDetail', { chatsToken, systemRoleToken })}>
24
27
  <TokenTag
@@ -1,6 +1,7 @@
1
1
  import { ChatInputArea } from '@lobehub/ui';
2
2
  import { useResponsive } from 'antd-style';
3
- import { memo, useState } from 'react';
3
+ import dynamic from 'next/dynamic';
4
+ import { Suspense, memo, useState } from 'react';
4
5
  import { useTranslation } from 'react-i18next';
5
6
 
6
7
  import { CHAT_TEXTAREA_HEIGHT } from '@/const/layoutTokens';
@@ -8,11 +9,12 @@ import { useSessionStore } from '@/store/session';
8
9
 
9
10
  import ActionLeft from './ActionBar/ActionLeft';
10
11
  import ActionsRight from './ActionBar/ActionRight';
11
- import Token from './ActionBar/Token';
12
12
  import Desktop from './Desktop';
13
13
  import Footer from './Footer';
14
14
  import Mobile from './Mobile';
15
15
 
16
+ const Token = dynamic(() => import('./ActionBar/Token'), { ssr: false });
17
+
16
18
  const ChatInput = () => {
17
19
  const { t } = useTranslation('common');
18
20
  const [expand, setExpand] = useState<boolean>(false);
@@ -33,7 +35,9 @@ const ChatInput = () => {
33
35
  actions={
34
36
  <>
35
37
  <ActionLeft />
36
- <Token input={message} />
38
+ <Suspense>
39
+ <Token input={message} />
40
+ </Suspense>
37
41
  </>
38
42
  }
39
43
  actionsRight={<ActionsRight />}
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import { useRouter } from 'next/navigation';
4
+ import { memo, useEffect } from 'react';
5
+
6
+ import { useSessionStore } from '@/store/session';
7
+ import { sessionSelectors } from '@/store/session/selectors';
8
+
9
+ const Redirect = memo(() => {
10
+ const router = useRouter();
11
+
12
+ useEffect(() => {
13
+ useSessionStore.persist.onFinishHydration(() => {
14
+ const store = useSessionStore.getState();
15
+ const hasSession = sessionSelectors.hasSessionList(store);
16
+
17
+ if (hasSession) {
18
+ router.push('/chat');
19
+ store.switchSession();
20
+ } else {
21
+ router.push('/welcome');
22
+ }
23
+ });
24
+ }, []);
25
+
26
+ return null;
27
+ });
28
+
29
+ export default Redirect;
@@ -1,31 +1,20 @@
1
1
  'use client';
2
2
 
3
- import { memo } from 'react';
4
-
5
- import {
6
- useEffectAfterSessionHydrated,
7
- useSessionHydrated,
8
- useSessionStore,
9
- } from '@/store/session';
10
- import { sessionSelectors } from '@/store/session/selectors';
11
-
12
- import Welcome from '../welcome';
13
- import Loading from './Loading';
14
-
15
- const Home = memo(() => {
16
- const hydrated = useSessionHydrated();
17
- const [hasSession, switchSession] = useSessionStore((s) => [
18
- sessionSelectors.hasSessionList(s),
19
- s.switchSession,
20
- ]);
21
-
22
- useEffectAfterSessionHydrated(() => {
23
- if (hasSession) switchSession();
24
- }, [hasSession]);
25
-
26
- if (!hydrated) return <Loading />;
27
-
28
- return !hasSession ? <Welcome /> : <Loading />;
29
- });
30
-
31
- export default Home;
3
+ import { Icon, Logo } from '@lobehub/ui';
4
+ import { t } from 'i18next';
5
+ import { Loader2 } from 'lucide-react';
6
+ import { Center, Flexbox } from 'react-layout-kit';
7
+
8
+ const Loading = () => (
9
+ <Flexbox height={'100vh'} width={'100%'}>
10
+ <Center flex={1} gap={12} width={'100%'}>
11
+ <Logo extra={'Chat'} size={48} type={'combine'} />
12
+ <Center gap={16} horizontal>
13
+ <Icon icon={Loader2} spin />
14
+ {t('appInitializing', { ns: 'common' })}
15
+ </Center>
16
+ </Center>
17
+ </Flexbox>
18
+ );
19
+
20
+ export default Loading;
@@ -1,6 +1,7 @@
1
1
  import { createStyles } from 'antd-style';
2
- import { encode } from 'gpt-tokenizer';
3
- import { memo, useMemo } from 'react';
2
+ import { memo } from 'react';
3
+
4
+ import { useTokenCount } from '@/hooks/useTokenCount';
4
5
 
5
6
  const useStyles = createStyles(
6
7
  ({ css, token }) => css`
@@ -17,7 +18,7 @@ const useStyles = createStyles(
17
18
 
18
19
  const TokenTag = memo<{ systemRole: string }>(({ systemRole }) => {
19
20
  const { styles } = useStyles();
20
- const value = useMemo(() => encode(systemRole).length, [systemRole]);
21
+ const value = useTokenCount(systemRole);
21
22
 
22
23
  return <div className={styles}>{value}</div>;
23
24
  });
package/src/app/page.tsx CHANGED
@@ -1,7 +1,11 @@
1
1
  import Page from './home';
2
+ import Redirect from './home/Redirect';
2
3
 
3
- const Index = () => {
4
- return <Page />;
5
- };
4
+ const Index = () => (
5
+ <>
6
+ <Page />
7
+ <Redirect />
8
+ </>
9
+ );
6
10
 
7
11
  export default Index;
@@ -3,10 +3,11 @@ import Link from 'next/link';
3
3
  import { PropsWithChildren, memo } from 'react';
4
4
  import { Center, Flexbox } from 'react-layout-kit';
5
5
 
6
- import AppLayout from '../../layout/AppLayout';
7
- import { useStyles } from './features/Banner/style';
6
+ import AppLayout from '@/layout/AppLayout';
8
7
 
9
- const WelcomeLayout = memo<PropsWithChildren>(({ children }) => {
8
+ import { useStyles } from '../features/Banner/style';
9
+
10
+ const Desktop = memo<PropsWithChildren>(({ children }) => {
10
11
  const { styles } = useStyles();
11
12
  return (
12
13
  <AppLayout>
@@ -28,4 +29,4 @@ const WelcomeLayout = memo<PropsWithChildren>(({ children }) => {
28
29
  );
29
30
  });
30
31
 
31
- export default WelcomeLayout;
32
+ export default Desktop;
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+
3
+ import { useResponsive } from 'antd-style';
4
+ import { PropsWithChildren, memo } from 'react';
5
+
6
+ import Mobile from '../MobileLayout';
7
+ import Desktop from './Desktop';
8
+
9
+ const ResponsiveLayout = memo<PropsWithChildren>(({ children }) => {
10
+ const { mobile } = useResponsive();
11
+
12
+ return mobile ? <Mobile>{children}</Mobile> : <Desktop>{children}</Desktop>;
13
+ });
14
+
15
+ export default ResponsiveLayout;
@@ -1,11 +1,14 @@
1
+ 'use client';
2
+
1
3
  import { Logo, MobileNavBar } from '@lobehub/ui';
2
4
  import { useTheme } from 'antd-style';
3
5
  import { PropsWithChildren, memo } from 'react';
4
6
 
5
- import AppMobileLayout from '../../layout/AppMobileLayout';
7
+ import AppMobileLayout from '@/layout/AppMobileLayout';
6
8
 
7
- const WelcomeLayout = memo<PropsWithChildren>(({ children }) => {
9
+ const MobileLayout = memo<PropsWithChildren>(({ children }) => {
8
10
  const theme = useTheme();
11
+
9
12
  return (
10
13
  <AppMobileLayout
11
14
  navBar={<MobileNavBar center={<Logo type={'text'} />} />}
@@ -17,4 +20,4 @@ const WelcomeLayout = memo<PropsWithChildren>(({ children }) => {
17
20
  );
18
21
  });
19
22
 
20
- export default WelcomeLayout;
23
+ export default MobileLayout;
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { GridShowcase, Icon } from '@lobehub/ui';
2
4
  import { useSize } from 'ahooks';
3
5
  import { Button, Upload } from 'antd';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { ActionIcon, DiscordIcon } from '@lobehub/ui';
2
4
  import { useTheme } from 'antd-style';
3
5
  import { Book, Github } from 'lucide-react';
@@ -0,0 +1,22 @@
1
+ import { PropsWithChildren } from 'react';
2
+
3
+ import { isMobileDevice } from '@/utils/responsive';
4
+
5
+ import Desktop from './DesktopLayout';
6
+ import Mobile from './MobileLayout';
7
+ import Footer from './features/Footer';
8
+
9
+ const Layout = ({ children }: PropsWithChildren) => {
10
+ const mobile = isMobileDevice();
11
+
12
+ return mobile ? (
13
+ <Mobile>{children}</Mobile>
14
+ ) : (
15
+ <Desktop>
16
+ {children}
17
+ <Footer />
18
+ </Desktop>
19
+ );
20
+ };
21
+
22
+ export default Layout;
@@ -1,7 +1,16 @@
1
- import Page from './index';
1
+ import { t } from 'i18next';
2
+ import { Metadata } from 'next';
2
3
 
3
- const Index = () => {
4
- return <Page />;
4
+ import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
5
+
6
+ import Banner from './features/Banner';
7
+
8
+ export const generateMetadata = (): Metadata => ({
9
+ title: genSiteHeadTitle(t('header', { ns: 'welcome' })),
10
+ });
11
+
12
+ const Page = () => {
13
+ return <Banner />;
5
14
  };
6
15
 
7
- export default Index;
16
+ export default Page;
@@ -1,7 +1,6 @@
1
1
  import { EditableMessage, FormGroup, TokenTag } from '@lobehub/ui';
2
2
  import { Button } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
- import { encode } from 'gpt-tokenizer';
5
4
  import { Bot } from 'lucide-react';
6
5
  import { memo, useState } from 'react';
7
6
  import { useTranslation } from 'react-i18next';
@@ -9,6 +8,7 @@ import { Flexbox } from 'react-layout-kit';
9
8
 
10
9
  import { FORM_STYLE } from '@/const/layoutTokens';
11
10
  import { ModelTokens } from '@/const/modelTokens';
11
+ import { useTokenCount } from '@/hooks/useTokenCount';
12
12
 
13
13
  import { useStore } from '../store';
14
14
 
@@ -26,11 +26,8 @@ const AgentPrompt = memo(() => {
26
26
  const { styles } = useStyles();
27
27
  const [editing, setEditing] = useState(false);
28
28
  const updateConfig = useStore((s) => s.setAgentConfig);
29
- const [systemRole, model, systemTokenCount] = useStore((s) => [
30
- s.config.systemRole,
31
- s.config.model,
32
- encode(s.config.systemRole || '').length,
33
- ]);
29
+ const [systemRole, model] = useStore((s) => [s.config.systemRole, s.config.model]);
30
+ const systemTokenCount = useTokenCount(systemRole);
34
31
 
35
32
  return (
36
33
  <FormGroup
@@ -0,0 +1,20 @@
1
+ import { startTransition, useEffect, useState } from 'react';
2
+
3
+ import { encodeAsync } from '@/utils/tokenizer';
4
+
5
+ export const useTokenCount = (input: string = '') => {
6
+ const [value, setNum] = useState(0);
7
+
8
+ useEffect(() => {
9
+ startTransition(() => {
10
+ encodeAsync(input)
11
+ .then(setNum)
12
+ .catch(() => {
13
+ // 兜底采用字符数
14
+ setNum(input.length);
15
+ });
16
+ });
17
+ }, [input]);
18
+
19
+ return value;
20
+ };
@@ -2,7 +2,9 @@ import { chatHelpers } from '@/store/session/helpers';
2
2
  import { LanguageModel } from '@/types/llm';
3
3
  import { OpenAIChatMessage, OpenAIStreamPayload } from '@/types/openai';
4
4
 
5
- export const promptSummaryTitle = (messages: OpenAIChatMessage[]): Partial<OpenAIStreamPayload> => {
5
+ export const promptSummaryTitle = async (
6
+ messages: OpenAIChatMessage[],
7
+ ): Promise<Partial<OpenAIStreamPayload>> => {
6
8
  const finalMessages: OpenAIChatMessage[] = [
7
9
  {
8
10
  content:
@@ -17,7 +19,7 @@ export const promptSummaryTitle = (messages: OpenAIChatMessage[]): Partial<OpenA
17
19
  },
18
20
  ];
19
21
  // 如果超过 4k,则使用 GPT3.5 16K 模型
20
- const tokens = chatHelpers.getMessagesTokenCount(finalMessages);
22
+ const tokens = await chatHelpers.getMessagesTokenCount(finalMessages);
21
23
  let model: LanguageModel | undefined = undefined;
22
24
  if (tokens > 4000) {
23
25
  model = LanguageModel.GPT3_5_16K;
@@ -1,17 +1,8 @@
1
- const isDev = process.env.NODE_ENV === 'development';
2
-
3
- const prefix = isDev ? '-dev' : '';
4
-
5
1
  export const URLS = {
6
- plugins: '/api/plugins' + prefix,
2
+ plugins: '/api/plugins',
7
3
  };
8
4
 
9
5
  export const OPENAI_URLS = {
10
- chat: '/api/openai/chat' + prefix,
11
- models: '/api/openai/models' + prefix,
12
- };
13
-
14
- export const AZURE_OPENAI_URLS = {
15
- chat: '/api/azure-openai/chat' + prefix,
16
- models: '/api/azure-openai/models' + prefix,
6
+ chat: '/api/openai/chat',
7
+ models: '/api/openai/models',
17
8
  };
@@ -1,21 +1,26 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
- import { useSessionStore } from '../store';
3
+ import { SessionStore, useSessionStore } from '../store';
4
4
 
5
5
  export const useEffectAfterSessionHydrated = (
6
- fn: (session: typeof useSessionStore) => void,
6
+ fn: (session: typeof useSessionStore, store: SessionStore) => void,
7
7
  deps: any[] = [],
8
8
  ) => {
9
+ // const hasTrigger = useRef(false);
9
10
  useEffect(() => {
10
11
  const hasRehydrated = useSessionStore.persist.hasHydrated();
11
12
 
12
13
  if (hasRehydrated) {
13
- // 等价 useEffect 多次触发
14
- fn(useSessionStore);
14
+ // equal useEffect triggered multi-time
15
+ fn(useSessionStore, useSessionStore.getState());
15
16
  } else {
16
- // 等价于 useEffect 第一次触发
17
+ // keep onFinishHydration just are triggered only once
18
+ // if (hasTrigger.current) return;
19
+ //
20
+ // hasTrigger.current = true;
21
+ // equal useEffect first trigger
17
22
  useSessionStore.persist.onFinishHydration(() => {
18
- fn(useSessionStore);
23
+ fn(useSessionStore, useSessionStore.getState());
19
24
  });
20
25
  }
21
26
  }, deps);
@@ -285,7 +285,7 @@ export const chatMessage: StateCreator<
285
285
 
286
286
  if (!activeTopicId && chats.length >= 2) {
287
287
  const { saveToTopic, toggleTopic } = get();
288
- const id = saveToTopic();
288
+ const id = await saveToTopic();
289
289
  if (id) toggleTopic(id);
290
290
  }
291
291
  },
@@ -26,7 +26,7 @@ export interface ChatTopicAction {
26
26
  /**
27
27
  * 将当前消息保存为主题
28
28
  */
29
- saveToTopic: () => string | undefined;
29
+ saveToTopic: () => Promise<string | undefined>;
30
30
  /**
31
31
  * 切换主题
32
32
  * @param id - 要切换的主题的 ID
@@ -72,7 +72,7 @@ export const chatTopic: StateCreator<
72
72
  // 切换到默认 topic
73
73
  toggleTopic();
74
74
  },
75
- saveToTopic: () => {
75
+ saveToTopic: async () => {
76
76
  const session = sessionSelectors.currentSession(get());
77
77
  if (!session) return;
78
78
 
@@ -116,7 +116,7 @@ export const chatTopic: StateCreator<
116
116
  output += x;
117
117
  dispatchTopic({ id: topicId, key: 'title', type: 'updateChatTopic', value: output });
118
118
  },
119
- params: promptSummaryTitle(messages),
119
+ params: await promptSummaryTitle(messages),
120
120
  });
121
121
 
122
122
  return topicId;
@@ -1,9 +1,8 @@
1
- import { encode } from 'gpt-tokenizer';
2
-
3
1
  import { OpenAIChatMessage } from '@/types/openai';
2
+ import { encodeAsync } from '@/utils/tokenizer';
4
3
 
5
- export const getMessagesTokenCount = (messages: OpenAIChatMessage[]) =>
6
- encode(messages.map((m) => m.content).join('')).length;
4
+ export const getMessagesTokenCount = async (messages: OpenAIChatMessage[]) =>
5
+ encodeAsync(messages.map((m) => m.content).join(''));
7
6
 
8
7
  export const chatHelpers = {
9
8
  getMessagesTokenCount,
@@ -79,3 +79,8 @@ export const currentChatsWithHistoryConfig = (s: SessionStore): ChatMessage[] =>
79
79
 
80
80
  return getSlicedMessagesWithConfig(chats, config);
81
81
  };
82
+
83
+ export const chatsMessageString = (s: SessionStore): string => {
84
+ const chats = currentChatsWithHistoryConfig(s);
85
+ return chats.map((m) => m.content).join('');
86
+ };
@@ -1,14 +1,18 @@
1
- import { currentChats, currentChatsWithGuideMessage, getChatsById } from './chat';
2
- import { chatsTokenCount, systemRoleTokenCount, totalTokenCount } from './token';
1
+ import {
2
+ chatsMessageString,
3
+ currentChats,
4
+ currentChatsWithGuideMessage,
5
+ currentChatsWithHistoryConfig,
6
+ getChatsById,
7
+ } from './chat';
3
8
  import { currentTopics, getTopicMessages } from './topic';
4
9
 
5
10
  export const chatSelectors = {
6
- chatsTokenCount,
11
+ chatsMessageString,
7
12
  currentChats,
8
13
  currentChatsWithGuideMessage,
14
+ currentChatsWithHistoryConfig,
9
15
  getChatsById,
10
- systemRoleTokenCount,
11
- totalTokenCount,
12
16
  };
13
17
 
14
18
  export const topicSelectors = {
@@ -95,7 +95,7 @@ export const createSessionSlice: StateCreator<
95
95
 
96
96
  dispatchSession({ session: newSession, type: 'addSession' });
97
97
 
98
- await switchSession(newSession.id);
98
+ switchSession(newSession.id);
99
99
 
100
100
  return newSession.id;
101
101
  },
@@ -0,0 +1,18 @@
1
+ import { headers } from 'next/headers';
2
+ import { UAParser } from 'ua-parser-js';
3
+
4
+ /**
5
+ * check mobile device in server
6
+ */
7
+ export const isMobileDevice = () => {
8
+ if (typeof process === 'undefined') {
9
+ throw new Error('[Server method] you are importing a server-only module outside of server');
10
+ }
11
+
12
+ const { get } = headers();
13
+ const ua = get('user-agent');
14
+
15
+ const device = new UAParser(ua || '').getDevice();
16
+
17
+ return device.type === 'mobile';
18
+ };
@@ -0,0 +1,5 @@
1
+ export const encodeAsync = async (str: string) => {
2
+ const { encode } = await import('gpt-tokenizer');
3
+
4
+ return encode(str).length;
5
+ };
@@ -1,23 +0,0 @@
1
- import { Icon, Logo } from '@lobehub/ui';
2
- import { Loader2 } from 'lucide-react';
3
- import { memo } from 'react';
4
- import { useTranslation } from 'react-i18next';
5
- import { Center, Flexbox } from 'react-layout-kit';
6
-
7
- const Loading = memo(() => {
8
- const { t } = useTranslation('common');
9
-
10
- return (
11
- <Flexbox height={'100vh'} width={'100%'}>
12
- <Center flex={1} gap={12} width={'100%'}>
13
- <Logo extra={'Chat'} size={48} type={'combine'} />
14
- <Center gap={16} horizontal>
15
- <Icon icon={Loader2} spin />
16
- {t('appInitializing')}
17
- </Center>
18
- </Center>
19
- </Flexbox>
20
- );
21
- });
22
-
23
- export default Loading;
@@ -1,39 +0,0 @@
1
- 'use client';
2
-
3
- import { useResponsive } from 'antd-style';
4
- import Head from 'next/head';
5
- import { memo } from 'react';
6
- import { useTranslation } from 'react-i18next';
7
-
8
- import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
9
-
10
- import Banner from './features/Banner';
11
- import Footer from './features/Footer';
12
- import Layout from './layout.desktop';
13
- import Mobile from './layout.mobile';
14
-
15
- const Welcome = memo(() => {
16
- const { mobile } = useResponsive();
17
- const { t } = useTranslation('welcome');
18
- const pageTitle = genSiteHeadTitle(t('header'));
19
-
20
- return (
21
- <>
22
- <Head>
23
- <title>{pageTitle}</title>
24
- </Head>
25
- {mobile ? (
26
- <Mobile>
27
- <Banner />
28
- </Mobile>
29
- ) : (
30
- <Layout>
31
- <Banner />
32
- <Footer />
33
- </Layout>
34
- )}
35
- </>
36
- );
37
- });
38
-
39
- export default Welcome;
@@ -1,9 +0,0 @@
1
- import { createLobeChatPluginGateway } from '@lobehub/chat-plugins-gateway';
2
-
3
- import { PLUGINS_INDEX_URL } from '@/const/url';
4
-
5
- export const config = {
6
- runtime: 'edge',
7
- };
8
-
9
- export default createLobeChatPluginGateway({ pluginsIndexUrl: PLUGINS_INDEX_URL });
@@ -1,28 +0,0 @@
1
- import { encode } from 'gpt-tokenizer';
2
-
3
- import { agentSelectors } from '@/store/session/slices/agentConfig';
4
-
5
- import type { SessionStore } from '../../../store';
6
- import { currentChatsWithHistoryConfig } from './chat';
7
-
8
- // TODO: We should find a smaller package to do this. gpt-tokenizer has 2M Size
9
- const count = (text: string) => encode(text).length;
10
-
11
- export const systemRoleSel = (s: SessionStore): string => {
12
- const config = agentSelectors.currentAgentConfig(s);
13
-
14
- return config.systemRole;
15
- };
16
-
17
- export const systemRoleTokenCount = (s: SessionStore): number => {
18
- const systemRole = systemRoleSel(s);
19
-
20
- return count(systemRole || '');
21
- };
22
-
23
- export const chatsTokenCount = (s: SessionStore): number => {
24
- const chats = currentChatsWithHistoryConfig(s);
25
- return count(chats.map((m) => m.content).join(''));
26
- };
27
-
28
- export const totalTokenCount = (s: SessionStore) => chatsTokenCount(s) + systemRoleTokenCount(s);
File without changes
File without changes