@lobehub/chat 0.69.0 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.github/workflows/contributor-help.yml +2 -4
  3. package/CHANGELOG.md +50 -0
  4. package/package.json +3 -3
  5. package/src/const/url.ts +3 -0
  6. package/src/features/MobileTabBar/index.tsx +3 -1
  7. package/src/features/SideBar/TopActions.tsx +4 -3
  8. package/src/pages/api/openai.ts +33 -36
  9. package/src/pages/chat/features/Conversation/ChatList/Plugins/IFrameRender/index.tsx +1 -1
  10. package/src/pages/chat/features/Conversation/ChatList/Plugins/IFrameRender/utils.ts +1 -1
  11. package/src/pages/chat/features/Conversation/ChatList/Plugins/SystemJsRender/index.tsx +1 -1
  12. package/src/pages/chat/features/Header/Mobile.tsx +6 -8
  13. package/src/pages/chat/features/Header/PC.tsx +15 -16
  14. package/src/pages/chat/features/Header/index.tsx +3 -3
  15. package/src/pages/chat/{SessionList → features/SessionList}/Inbox.tsx +9 -2
  16. package/src/pages/chat/{SessionList → features/SessionList}/List/index.tsx +10 -1
  17. package/src/pages/chat/features/Sidebar/index.tsx +6 -2
  18. package/src/pages/chat/index.page.tsx +9 -11
  19. package/src/pages/chat/layout.tsx +6 -1
  20. package/src/pages/chat/{[id] → mobile}/index.page.tsx +7 -17
  21. package/src/pages/chat/mobile.tsx +2 -2
  22. package/src/pages/chat/{[id]/setting → setting}/Header.tsx +3 -4
  23. package/src/pages/index.page.tsx +10 -4
  24. package/src/pages/settings/Header.tsx +4 -2
  25. package/src/pages/welcome/Banner/index.tsx +11 -3
  26. package/src/services/chatModel.ts +1 -2
  27. package/src/store/middleware/createHyperStorage/index.ts +95 -0
  28. package/src/store/middleware/createHyperStorage/keyMapper.ts +53 -0
  29. package/src/store/middleware/createHyperStorage/type.ts +14 -0
  30. package/src/store/middleware/createHyperStorage/urlSearch.ts +17 -0
  31. package/src/store/plugin/selectors.ts +1 -1
  32. package/src/store/session/slices/session/action.ts +18 -9
  33. package/src/store/session/slices/session/initialState.ts +2 -3
  34. package/src/store/session/slices/session/selectors/index.ts +6 -0
  35. package/src/store/session/store.ts +16 -8
  36. package/src/types/openai.ts +23 -0
  37. package/src/pages/chat/[id]/layout.tsx +0 -34
  38. package/src/pages/chat/[id]/mobile.tsx +0 -31
  39. package/src/store/middleware/createHashStorage.ts +0 -49
  40. /package/src/pages/chat/{SessionList → features/SessionList}/CollapseGroup.tsx +0 -0
  41. /package/src/pages/chat/{SessionList → features/SessionList}/Header.tsx +0 -0
  42. /package/src/pages/chat/{SessionList → features/SessionList}/List/AddButton.tsx +0 -0
  43. /package/src/pages/chat/{SessionList → features/SessionList}/List/Item/Actions.tsx +0 -0
  44. /package/src/pages/chat/{SessionList → features/SessionList}/List/Item/index.tsx +0 -0
  45. /package/src/pages/chat/{SessionList → features/SessionList}/List/SkeletonList.tsx +0 -0
  46. /package/src/pages/chat/{SessionList → features/SessionList}/index.tsx +0 -0
  47. /package/src/pages/chat/{[id]/setting → setting}/index.page.tsx +0 -0
  48. /package/src/pages/chat/{[id]/setting → setting}/mobile.tsx +0 -0
package/.eslintrc.js CHANGED
@@ -12,5 +12,6 @@ config.rules['unicorn/prefer-code-point'] = 0;
12
12
  config.rules['no-extra-boolean-cast'] = 0;
13
13
  config.rules['unicorn/no-useless-undefined'] = 0;
14
14
  config.rules['react/no-unknown-property'] = 0;
15
+ config.rules['unicorn/prefer-ternary'] = 0;
15
16
 
16
17
  module.exports = config;
@@ -1,9 +1,7 @@
1
1
  name: Contributor Helper
2
2
 
3
3
  on:
4
- # 🌏 Think about the planet! No need to update stats too frequently
5
4
  schedule: [{ cron: '0 18 * * *' }]
6
- # 💡 The following line lets you run workflow manually from the action tab!
7
5
  workflow_dispatch:
8
6
  jobs:
9
7
  contributor:
@@ -21,8 +19,8 @@ jobs:
21
19
  - name: Commit and push if changed
22
20
  run: |-
23
21
  git diff
24
- git config --global user.email "actions@github.com"
25
- git config --global user.name "github-actions"
22
+ git config --global user.name "lobehubbot"
23
+ git config --global user.email "i@lobehub.com"
26
24
  git pull
27
25
  git add -A
28
26
  git commit -m "🤖 docs: Auto update contributors" || exit 0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.70.0](https://github.com/lobehub/lobe-chat/compare/v0.69.1...v0.70.0)
6
+
7
+ <sup>Released on **2023-09-08**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Refactor to url state.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Refactor to url state, closes [#157](https://github.com/lobehub/lobe-chat/issues/157) ([2efac2b](https://github.com/lobehub/lobe-chat/commit/2efac2b))
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.69.1](https://github.com/lobehub/lobe-chat/compare/v0.69.0...v0.69.1)
31
+
32
+ <sup>Released on **2023-09-06**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Mirgrate openai-edge to openai.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **misc**: Mirgrate openai-edge to openai, closes [#145](https://github.com/lobehub/lobe-chat/issues/145) ([75ee574](https://github.com/lobehub/lobe-chat/commit/75ee574))
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.69.0](https://github.com/lobehub/lobe-chat/compare/v0.68.1...v0.69.0)
6
56
 
7
57
  <sup>Released on **2023-09-06**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.69.0",
3
+ "version": "0.70.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -66,7 +66,7 @@
66
66
  "@emoji-mart/data": "^1",
67
67
  "@emoji-mart/react": "^1",
68
68
  "@icons-pack/react-simple-icons": "^9",
69
- "@lobehub/chat-plugin-sdk": "^1.15.1",
69
+ "@lobehub/chat-plugin-sdk": "^1.17.0",
70
70
  "@lobehub/chat-plugins-gateway": "^1.5.0",
71
71
  "@lobehub/ui": "latest",
72
72
  "@vercel/analytics": "^1",
@@ -85,7 +85,7 @@
85
85
  "lucide-react": "latest",
86
86
  "nanoid": "^4",
87
87
  "next": "13.4.7",
88
- "openai-edge": "^1",
88
+ "openai": "^4.4.0",
89
89
  "polished": "^4",
90
90
  "react": "^18",
91
91
  "react-dom": "^18",
package/src/const/url.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import pkg from '../../package.json';
2
+ import { INBOX_SESSION_ID } from './session';
2
3
 
3
4
  export const GITHUB = pkg.homepage;
4
5
  export const CHANGELOG = `${pkg.homepage}/blob/master/CHANGELOG.md`;
@@ -8,3 +9,5 @@ export const DISCORD = 'https://discord.gg/AYFPHvv2jT';
8
9
 
9
10
  export const PLUGINS_INDEX_URL =
10
11
  process.env.PLUGINS_INDEX_URL ?? 'https://chat-plugins.lobehub.com';
12
+
13
+ export const SESSION_CHAT_URL = (id: string = INBOX_SESSION_ID) => `/chat#session=${id}`;
@@ -7,6 +7,7 @@ import { memo, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
 
9
9
  import { useGlobalStore } from '@/store/global';
10
+ import { useSessionStore } from '@/store/session';
10
11
 
11
12
  const useStyles = createStyles(({ css, token }) => ({
12
13
  active: css`
@@ -20,6 +21,7 @@ export default memo<{ className?: string }>(({ className }) => {
20
21
  const [tab, setTab] = useGlobalStore((s) => [s.sidebarKey, s.switchSideBar]);
21
22
  const { t } = useTranslation('common');
22
23
  const { styles } = useStyles();
24
+ const switchSession = useSessionStore((s) => s.switchSession);
23
25
  const items: MobileTabBarProps['items'] = useMemo(
24
26
  () => [
25
27
  {
@@ -28,7 +30,7 @@ export default memo<{ className?: string }>(({ className }) => {
28
30
  ),
29
31
  key: 'chat',
30
32
  onClick: () => {
31
- Router.push('/chat');
33
+ switchSession();
32
34
  },
33
35
  title: t('tab.chat'),
34
36
  },
@@ -5,6 +5,7 @@ import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import { GlobalStore } from '@/store/global';
8
+ import { useSessionStore } from '@/store/session';
8
9
 
9
10
  export interface TopActionProps {
10
11
  setTab: GlobalStore['switchSideBar'];
@@ -13,7 +14,7 @@ export interface TopActionProps {
13
14
 
14
15
  const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
15
16
  const { t } = useTranslation('common');
16
-
17
+ const switchBackToChat = useSessionStore((s) => s.switchBackToChat);
17
18
  return (
18
19
  <>
19
20
  <ActionIcon
@@ -22,7 +23,7 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
22
23
  onClick={() => {
23
24
  // 如果已经在 chat 路径下了,那么就不用再跳转了
24
25
  if (Router.asPath.startsWith('/chat')) return;
25
- Router.push('/chat');
26
+ switchBackToChat();
26
27
  setTab('chat');
27
28
  }}
28
29
  size="large"
@@ -31,7 +32,7 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
31
32
  <ActionIcon
32
33
  active={tab === 'market'}
33
34
  icon={Sticker}
34
- onClick={() => setTab('market')}
35
+ // onClick={() => setTab('market')}
35
36
  size="large"
36
37
  title={t('tab.market')}
37
38
  />
@@ -1,5 +1,5 @@
1
- import { OpenAIStream, OpenAIStreamCallbacks, StreamingTextResponse } from 'ai';
2
- import { Configuration, OpenAIApi } from 'openai-edge';
1
+ import { OpenAIStream, StreamingTextResponse } from 'ai';
2
+ import OpenAI, { ClientOptions } from 'openai';
3
3
 
4
4
  import { getServerConfig } from '@/config/server';
5
5
  import { createErrorResponse } from '@/pages/api/error';
@@ -10,25 +10,29 @@ import { OpenAIStreamPayload } from '@/types/openai';
10
10
  export const createOpenAI = (userApiKey: string | null, endpoint?: string | null) => {
11
11
  const { OPENAI_API_KEY, OPENAI_PROXY_URL } = getServerConfig();
12
12
 
13
- const config = new Configuration({
13
+ const baseURL = endpoint ? endpoint : OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined;
14
+
15
+ const config: ClientOptions = {
14
16
  apiKey: !userApiKey ? OPENAI_API_KEY : userApiKey,
15
- });
17
+ };
16
18
 
17
- const basePath = endpoint ? endpoint : OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined;
19
+ // a bug with openai: https://github.com/openai/openai-node/issues/283
20
+ // TODO: should refactor when openai fix the bug
21
+ if (baseURL) {
22
+ config.baseURL = baseURL;
23
+ }
18
24
 
19
- return new OpenAIApi(config, basePath);
25
+ return new OpenAI(config);
20
26
  };
21
27
 
22
28
  interface CreateChatCompletionOptions {
23
29
  OPENAI_API_KEY: string | null;
24
- callbacks?: (payload: OpenAIStreamPayload) => OpenAIStreamCallbacks;
25
30
  endpoint?: string | null;
26
31
  payload: OpenAIStreamPayload;
27
32
  }
28
33
 
29
34
  export const createChatCompletion = async ({
30
35
  payload,
31
- callbacks,
32
36
  OPENAI_API_KEY,
33
37
  endpoint,
34
38
  }: CreateChatCompletionOptions) => {
@@ -47,37 +51,30 @@ export const createChatCompletion = async ({
47
51
 
48
52
  // ============ 2. 发送请求 ============ //
49
53
 
50
- const requestParams = { messages: formatMessages, stream: true, ...params };
51
-
52
- let response: Response;
53
-
54
54
  try {
55
- response = await openai.createChatCompletion(requestParams);
55
+ const response = await openai.chat.completions.create({
56
+ messages: formatMessages,
57
+ ...params,
58
+ stream: true,
59
+ });
60
+ const stream = OpenAIStream(response);
61
+ return new StreamingTextResponse(stream);
56
62
  } catch (error) {
57
- // 如果 await 超时报错,说明是 OpenAI 服务端的问题
58
- return createErrorResponse(ChatErrorType.GatewayTimeout, { message: error });
59
- }
60
-
61
- // ============ 4. 处理异常响应 ============ //
62
- if (!response.ok) {
63
- let error;
64
-
65
- try {
66
- // 正常情况下应该是 OpenAI 的 JSON
67
- error = await response.clone().json();
68
- } catch {
69
- // 如果不是 JSON,那么可能是其他接口端的响应,读 text 为结果
70
- const result = await response.text();
71
-
72
- error = { message: result };
63
+ // Check if the error is an OpenAI APIError
64
+ if (error instanceof OpenAI.APIError) {
65
+ return createErrorResponse(ChatErrorType.OpenAIBizError, {
66
+ endpoint: !!endpoint ? endpoint : undefined,
67
+ error: error.error ?? error.cause,
68
+ });
73
69
  }
74
70
 
75
- return createErrorResponse(ChatErrorType.OpenAIBizError, { ...error, endpoint });
76
- }
77
-
78
- // ============ 5. 发送正常相应 ============ //
79
-
80
- const stream = OpenAIStream(response, callbacks?.(requestParams));
71
+ // track the error that not an OpenAI APIError
72
+ console.error(error);
81
73
 
82
- return new StreamingTextResponse(stream);
74
+ // return as a GatewayTimeout error
75
+ return createErrorResponse(ChatErrorType.InternalServerError, {
76
+ endpoint,
77
+ error: JSON.stringify(error),
78
+ });
79
+ }
83
80
  };
@@ -1,4 +1,4 @@
1
- import { PluginRenderProps } from '@lobehub/chat-plugin-sdk';
1
+ import { PluginRenderProps } from '@lobehub/chat-plugin-sdk/client';
2
2
  import { Skeleton } from 'antd';
3
3
  import { memo, useEffect, useRef, useState } from 'react';
4
4
 
@@ -1,4 +1,4 @@
1
- import { PluginChannel } from '@lobehub/chat-plugin-sdk';
1
+ import { PluginChannel } from '@lobehub/chat-plugin-sdk/client';
2
2
 
3
3
  export const onPluginReady = (e: MessageEvent, onReady: () => void) => {
4
4
  if (e.data.type === PluginChannel.pluginReadyForRender) {
@@ -1,4 +1,4 @@
1
- import { PluginRender, PluginRenderProps } from '@lobehub/chat-plugin-sdk';
1
+ import { PluginRender, PluginRenderProps } from '@lobehub/chat-plugin-sdk/client';
2
2
  import { Skeleton } from 'antd';
3
3
  import { memo, useEffect, useState } from 'react';
4
4
 
@@ -4,21 +4,19 @@ import Router from 'next/router';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
- import { INBOX_SESSION_ID } from '@/const/session';
8
7
  import { useGlobalStore } from '@/store/global';
9
8
  import { useSessionStore } from '@/store/session';
10
- import { agentSelectors } from '@/store/session/selectors';
9
+ import { agentSelectors, sessionSelectors } from '@/store/session/selectors';
11
10
 
12
- const MobileHeader = memo<{ settings?: boolean }>(({ settings = true }) => {
11
+ const MobileHeader = memo(() => {
13
12
  const { t } = useTranslation('common');
14
13
 
15
- const [id, title, model] = useSessionStore((s) => [
16
- s.activeId,
14
+ const [isInbox, title, model] = useSessionStore((s) => [
15
+ sessionSelectors.isInboxSession(s),
17
16
  agentSelectors.currentAgentTitle(s),
18
17
  agentSelectors.currentAgentModel(s),
19
18
  ]);
20
19
 
21
- const isInbox = id === INBOX_SESSION_ID;
22
20
  const [toggleConfig] = useGlobalStore((s) => [s.toggleMobileTopic]);
23
21
 
24
22
  const displayTitle = isInbox ? t('inbox.title') : title;
@@ -30,11 +28,11 @@ const MobileHeader = memo<{ settings?: boolean }>(({ settings = true }) => {
30
28
  right={
31
29
  <>
32
30
  <ActionIcon icon={LayoutList} onClick={() => toggleConfig()} />
33
- {settings && (
31
+ {!isInbox && (
34
32
  <ActionIcon
35
33
  icon={Settings}
36
34
  onClick={() => {
37
- Router.push(`/chat/${id}/setting`);
35
+ Router.push({ hash: location.hash, pathname: `/chat/setting` });
38
36
  }}
39
37
  />
40
38
  )}
@@ -7,31 +7,30 @@ import { memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
- import { INBOX_SESSION_ID } from '@/const/session';
11
10
  import { useGlobalStore } from '@/store/global';
12
11
  import { useSessionChatInit, useSessionStore } from '@/store/session';
13
- import { agentSelectors } from '@/store/session/selectors';
12
+ import { agentSelectors, sessionSelectors } from '@/store/session/selectors';
14
13
 
15
14
  import PluginTag from './PluginTag';
16
15
  import ShareButton from './ShareButton';
17
16
 
18
- const Header = memo<{ settings?: boolean }>(({ settings = true }) => {
17
+ const Header = memo(() => {
19
18
  const init = useSessionChatInit();
20
19
 
21
20
  const { t } = useTranslation('common');
22
21
 
23
- const [id, title, description, avatar, backgroundColor, model, plugins] = useSessionStore((s) => [
24
- s.activeId,
25
-
26
- agentSelectors.currentAgentTitle(s),
27
- agentSelectors.currentAgentDescription(s),
28
- agentSelectors.currentAgentAvatar(s),
29
- agentSelectors.currentAgentBackgroundColor(s),
30
- agentSelectors.currentAgentModel(s),
31
- agentSelectors.currentAgentPlugins(s),
32
- ]);
22
+ const [isInbox, title, description, avatar, backgroundColor, model, plugins] = useSessionStore(
23
+ (s) => [
24
+ sessionSelectors.isInboxSession(s),
25
+ agentSelectors.currentAgentTitle(s),
26
+ agentSelectors.currentAgentDescription(s),
27
+ agentSelectors.currentAgentAvatar(s),
28
+ agentSelectors.currentAgentBackgroundColor(s),
29
+ agentSelectors.currentAgentModel(s),
30
+ agentSelectors.currentAgentPlugins(s),
31
+ ],
32
+ );
33
33
 
34
- const isInbox = id === INBOX_SESSION_ID;
35
34
  const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
36
35
  s.preference.showChatSideBar,
37
36
  s.toggleChatSideBar,
@@ -77,11 +76,11 @@ const Header = memo<{ settings?: boolean }>(({ settings = true }) => {
77
76
  size={{ fontSize: 24 }}
78
77
  title={t('roleAndArchive')}
79
78
  />
80
- {settings && (
79
+ {!isInbox && (
81
80
  <ActionIcon
82
81
  icon={Settings}
83
82
  onClick={() => {
84
- Router.push(`/chat/${id}/setting`);
83
+ Router.push({ hash: location.hash, pathname: `/chat/setting` });
85
84
  }}
86
85
  size={{ fontSize: 24 }}
87
86
  title={t('header.session', { ns: 'setting' })}
@@ -4,12 +4,12 @@ import { memo } from 'react';
4
4
  import Mobile from './Mobile';
5
5
  import PC from './PC';
6
6
 
7
- const Header = memo<{ settings?: boolean }>((props) => {
7
+ const Header = memo(() => {
8
8
  const { mobile } = useResponsive();
9
9
 
10
- if (mobile) return <Mobile {...props} />;
10
+ if (mobile) return <Mobile />;
11
11
 
12
- return <PC {...props} />;
12
+ return <PC />;
13
13
  });
14
14
 
15
15
  export default Header;
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import { DEFAULT_INBOX_AVATAR } from '@/const/meta';
8
8
  import { INBOX_SESSION_ID } from '@/const/session';
9
+ import { SESSION_CHAT_URL } from '@/const/url';
9
10
  import { useSessionStore } from '@/store/session';
10
11
 
11
12
  const { Item } = List;
@@ -14,7 +15,7 @@ const Inbox = memo(() => {
14
15
  const ref = useRef(null);
15
16
  const isHovering = useHover(ref);
16
17
  const { t } = useTranslation('common');
17
- const [activeId] = useSessionStore((s) => [s.activeId]);
18
+ const [activeId, switchSession] = useSessionStore((s) => [s.activeId, s.switchSession]);
18
19
 
19
20
  const avatarRender = useMemo(
20
21
  () => (
@@ -29,7 +30,13 @@ const Inbox = memo(() => {
29
30
  );
30
31
 
31
32
  return (
32
- <Link href={'/chat'}>
33
+ <Link
34
+ href={SESSION_CHAT_URL()}
35
+ onClick={(e) => {
36
+ e.preventDefault();
37
+ switchSession(INBOX_SESSION_ID);
38
+ }}
39
+ >
33
40
  <Item
34
41
  active={activeId === INBOX_SESSION_ID}
35
42
  avatar={avatarRender}
@@ -2,6 +2,7 @@ import isEqual from 'fast-deep-equal';
2
2
  import Link from 'next/link';
3
3
  import { memo } from 'react';
4
4
 
5
+ import { SESSION_CHAT_URL } from '@/const/url';
5
6
  import { useSessionHydrated, useSessionStore } from '@/store/session';
6
7
  import { sessionSelectors } from '@/store/session/selectors';
7
8
 
@@ -11,6 +12,7 @@ import SkeletonList from './SkeletonList';
11
12
 
12
13
  const SessionList = memo(() => {
13
14
  const list = useSessionStore(sessionSelectors.sessionList, isEqual);
15
+ const activeSession = useSessionStore((s) => s.activeSession);
14
16
 
15
17
  const isInit = useSessionHydrated();
16
18
 
@@ -18,7 +20,14 @@ const SessionList = memo(() => {
18
20
  <SkeletonList />
19
21
  ) : list.length > 0 ? (
20
22
  list.map(({ id }) => (
21
- <Link href={`/chat/${id}`} key={id}>
23
+ <Link
24
+ href={SESSION_CHAT_URL(id)}
25
+ key={id}
26
+ onClick={(e) => {
27
+ e.preventDefault();
28
+ activeSession(id);
29
+ }}
30
+ >
22
31
  <SessionItem id={id} />
23
32
  </Link>
24
33
  ))
@@ -8,6 +8,8 @@ import { Flexbox } from 'react-layout-kit';
8
8
  import SafeSpacing from '@/components/SafeSpacing';
9
9
  import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
10
10
  import { useGlobalStore } from '@/store/global';
11
+ import { useSessionStore } from '@/store/session';
12
+ import { sessionSelectors } from '@/store/session/slices/session/selectors';
11
13
 
12
14
  import Mobile from './Mobile';
13
15
  import SystemRole from './SystemRole';
@@ -37,13 +39,15 @@ interface SideBarProps {
37
39
  mobile?: boolean;
38
40
  systemRole?: boolean;
39
41
  }
40
- const SideBar = memo<SideBarProps>(({ systemRole = true, mobile }) => {
42
+ const SideBar = memo<SideBarProps>(({ mobile }) => {
41
43
  const { styles } = useStyles(mobile);
42
44
  const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
43
45
  s.preference.showChatSideBar,
44
46
  s.toggleChatSideBar,
45
47
  ]);
46
48
 
49
+ const isInbox = useSessionStore(sessionSelectors.isInboxSession);
50
+
47
51
  const { t } = useTranslation('common');
48
52
 
49
53
  const topic = (
@@ -79,7 +83,7 @@ const SideBar = memo<SideBarProps>(({ systemRole = true, mobile }) => {
79
83
  style={{ flex: 'none', height: '100%', minWidth: CHAT_SIDEBAR_WIDTH }}
80
84
  >
81
85
  <SafeSpacing />
82
- {systemRole && <SystemRole mobile={mobile} />}
86
+ {!isInbox && <SystemRole mobile={mobile} />}
83
87
 
84
88
  {topic}
85
89
  </DraggablePanelContainer>
@@ -1,11 +1,10 @@
1
1
  import { useResponsive } from 'antd-style';
2
2
  import Head from 'next/head';
3
3
  import { memo } from 'react';
4
- import { useTranslation } from 'react-i18next';
5
4
  import { Flexbox } from 'react-layout-kit';
6
5
 
7
- import { INBOX_SESSION_ID } from '@/const/session';
8
- import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
6
+ import { useSessionStore } from '@/store/session';
7
+ import { agentSelectors } from '@/store/session/selectors';
9
8
  import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
10
9
 
11
10
  import Conversation from './features/Conversation';
@@ -15,13 +14,12 @@ import Layout from './layout';
15
14
  import Mobile from './mobile';
16
15
 
17
16
  const Chat = memo(() => {
18
- const { t } = useTranslation('common');
19
17
  const { mobile } = useResponsive();
20
- const pageTitle = genSiteHeadTitle(t('inbox.title'));
21
-
22
- useOnFinishHydrationSession(() => {
23
- useSessionStore.getState().activeSession(INBOX_SESSION_ID);
24
- });
18
+ const [avatar, title] = useSessionStore((s) => [
19
+ agentSelectors.currentAgentAvatar(s),
20
+ agentSelectors.currentAgentTitle(s),
21
+ ]);
22
+ const pageTitle = genSiteHeadTitle([avatar, title].filter(Boolean).join(' '));
25
23
 
26
24
  return (
27
25
  <>
@@ -32,10 +30,10 @@ const Chat = memo(() => {
32
30
  <Mobile />
33
31
  ) : (
34
32
  <Layout>
35
- <Header settings={false} />
33
+ <Header />
36
34
  <Flexbox flex={1} height={'calc(100vh - 64px)'} horizontal>
37
35
  <Conversation />
38
- <SideBar systemRole={false} />
36
+ <SideBar />
39
37
  </Flexbox>
40
38
  </Layout>
41
39
  )}
@@ -4,13 +4,18 @@ import { Flexbox } from 'react-layout-kit';
4
4
 
5
5
  import AppLayout from '@/layout/AppLayout';
6
6
  import { useSwitchSideBarOnInit } from '@/store/global';
7
+ import { usePluginStore } from '@/store/plugin';
7
8
 
8
- import { Sessions } from './SessionList';
9
+ import { Sessions } from './features/SessionList';
9
10
 
10
11
  const ChatLayout = memo<PropsWithChildren>(({ children }) => {
11
12
  const { mobile } = useResponsive();
13
+
12
14
  useSwitchSideBarOnInit('chat');
13
15
 
16
+ const useFetchPluginList = usePluginStore((s) => s.useFetchPluginList);
17
+ useFetchPluginList();
18
+
14
19
  return (
15
20
  <AppLayout>
16
21
  <Sessions />
@@ -1,20 +1,18 @@
1
- import { useResponsive } from 'antd-style';
2
1
  import Head from 'next/head';
3
2
  import { memo } from 'react';
4
3
  import { Flexbox } from 'react-layout-kit';
5
4
 
5
+ import AppMobileLayout from '@/layout/AppMobileLayout';
6
+ import Header from '@/pages/chat/features/Header';
6
7
  import { useSessionStore } from '@/store/session';
7
8
  import { agentSelectors } from '@/store/session/selectors';
8
9
  import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
9
10
 
10
11
  import Conversation from '../features/Conversation';
11
- import Header from '../features/Header';
12
12
  import SideBar from '../features/Sidebar';
13
- import Layout from './layout';
14
- import Mobile from './mobile';
13
+ import ChatLayout from '../layout';
15
14
 
16
15
  const Chat = memo(() => {
17
- const { mobile } = useResponsive();
18
16
  const [avatar, title] = useSessionStore((s) => [
19
17
  agentSelectors.currentAgentAvatar(s),
20
18
  agentSelectors.currentAgentTitle(s),
@@ -27,22 +25,14 @@ const Chat = memo(() => {
27
25
  <Head>
28
26
  <title>{pageTitle}</title>
29
27
  </Head>
30
- {mobile ? (
31
- <Mobile>
28
+ <AppMobileLayout navBar={<Header />}>
29
+ <ChatLayout>
32
30
  <Flexbox height={'calc(100vh - 44px)'} horizontal>
33
31
  <Conversation mobile />
34
32
  <SideBar mobile />
35
33
  </Flexbox>
36
- </Mobile>
37
- ) : (
38
- <Layout>
39
- <Header />
40
- <Flexbox flex={1} height={'calc(100vh - 64px)'} horizontal>
41
- <Conversation />
42
- <SideBar />
43
- </Flexbox>
44
- </Layout>
45
- )}
34
+ </ChatLayout>
35
+ </AppMobileLayout>
46
36
  </>
47
37
  );
48
38
  });
@@ -2,10 +2,10 @@ import { useTheme } from 'antd-style';
2
2
  import { memo } from 'react';
3
3
 
4
4
  import AppMobileLayout from '@/layout/AppMobileLayout';
5
- import Header from '@/pages/chat/SessionList/Header';
6
5
  import { useSwitchSideBarOnInit } from '@/store/global';
7
6
 
8
- import { Sessions } from './SessionList';
7
+ import { Sessions } from './features/SessionList';
8
+ import Header from './features/SessionList/Header';
9
9
 
10
10
  const ChatMobileLayout = memo(() => {
11
11
  useSwitchSideBarOnInit('chat');