@lobehub/chat 0.74.0 → 0.76.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 (110) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +26 -5
  3. package/README.zh-CN.md +25 -4
  4. package/docs/deploy-with-azure-openai.zh-CN.md +45 -0
  5. package/locales/en_US/market.json +12 -0
  6. package/locales/en_US/plugin.json +11 -2
  7. package/locales/en_US/setting.json +3 -1
  8. package/locales/ru_RU/market.json +12 -0
  9. package/locales/ru_RU/plugin.json +44 -49
  10. package/locales/ru_RU/setting.json +3 -1
  11. package/locales/zh_CN/common.json +1 -1
  12. package/locales/zh_CN/market.json +12 -0
  13. package/locales/zh_CN/plugin.json +19 -5
  14. package/locales/zh_CN/setting.json +62 -14
  15. package/next.config.mjs +3 -1
  16. package/package.json +5 -5
  17. package/scripts/i18nWorkflow/const.ts +19 -0
  18. package/scripts/i18nWorkflow/genDefaultLocale.ts +19 -0
  19. package/scripts/i18nWorkflow/genDiff.ts +50 -0
  20. package/scripts/i18nWorkflow/genResources.ts +26 -0
  21. package/scripts/i18nWorkflow/index.ts +15 -0
  22. package/scripts/i18nWorkflow/utils.ts +85 -0
  23. package/src/config/client.ts +14 -0
  24. package/src/config/server.ts +5 -0
  25. package/src/const/layoutTokens.ts +3 -1
  26. package/src/const/market.ts +15 -0
  27. package/src/const/settings.ts +2 -1
  28. package/src/const/url.ts +29 -4
  29. package/src/features/AgentSetting/AgentConfig/index.tsx +1 -2
  30. package/src/features/AgentSetting/AgentPlugin/MarketSettingModal.tsx +1 -1
  31. package/src/features/AgentSetting/store/initialState.ts +1 -1
  32. package/src/features/MobileTabBar/index.tsx +2 -2
  33. package/src/features/SideBar/TopActions.tsx +2 -2
  34. package/src/locales/default/market.ts +12 -0
  35. package/src/locales/default/setting.ts +1 -0
  36. package/src/locales/resources/en_US.ts +2 -0
  37. package/src/locales/resources/ru_RU.ts +2 -0
  38. package/src/locales/resources/zh_CN.ts +2 -0
  39. package/src/pages/api/openai/chat.api.ts +6 -10
  40. package/src/pages/api/openai/createAzureOpenai.ts +13 -9
  41. package/src/pages/chat/features/Conversation/Input/{Action.tsx → ActionBar/ActionLeft.tsx} +2 -2
  42. package/src/pages/chat/features/Conversation/Input/ActionBar/ActionRight.tsx +36 -0
  43. package/src/pages/chat/features/Conversation/Input/Desktop.tsx +40 -0
  44. package/src/pages/chat/features/Conversation/Input/Footer/SaveTopic.tsx +27 -0
  45. package/src/pages/chat/features/Conversation/Input/Footer/index.tsx +35 -0
  46. package/src/pages/chat/features/Conversation/Input/Mobile.tsx +12 -0
  47. package/src/pages/chat/features/Conversation/Input/index.tsx +20 -38
  48. package/src/pages/chat/features/SessionList/Header/Desktop.tsx +6 -13
  49. package/src/pages/chat/features/SessionList/Header/Mobile.tsx +1 -1
  50. package/src/pages/chat/features/SessionList/List/AddButton.tsx +1 -1
  51. package/src/pages/chat/features/SessionList/List/Item/index.tsx +4 -3
  52. package/src/pages/chat/features/SessionList/List/index.tsx +1 -3
  53. package/src/pages/chat/features/SessionList/Mobile.tsx +2 -12
  54. package/src/pages/chat/features/SessionSearchBar/index.tsx +26 -0
  55. package/src/pages/chat/features/Sidebar/Topic/index.tsx +18 -6
  56. package/src/pages/chat/features/Sidebar/TopicSearchBar/index.tsx +24 -0
  57. package/src/pages/chat/features/Sidebar/index.tsx +2 -9
  58. package/src/pages/market/features/AgentCard/AgentCardBanner.tsx +64 -0
  59. package/src/pages/market/features/AgentCard/AgentCardItem.tsx +57 -0
  60. package/src/pages/market/features/AgentCard/Loading.tsx +13 -0
  61. package/src/pages/market/features/AgentCard/index.tsx +37 -0
  62. package/src/pages/market/features/AgentCard/style.ts +22 -0
  63. package/src/pages/market/features/AgentSearchBar/index.tsx +26 -0
  64. package/src/pages/market/features/Header/Desktop.tsx +15 -4
  65. package/src/pages/market/features/Header/Mobile.tsx +7 -24
  66. package/src/pages/market/features/SideBar/AgentInfo/Comment.tsx +16 -0
  67. package/src/pages/market/features/SideBar/AgentInfo/Header.tsx +68 -0
  68. package/src/pages/market/features/SideBar/AgentInfo/Loading.tsx +61 -0
  69. package/src/pages/market/features/SideBar/AgentInfo/TokenTag.tsx +25 -0
  70. package/src/pages/market/features/SideBar/AgentInfo/index.tsx +68 -0
  71. package/src/pages/market/features/SideBar/AgentInfo/style.ts +46 -0
  72. package/src/pages/market/features/SideBar/Desktop.tsx +70 -0
  73. package/src/pages/market/features/SideBar/Mobile.tsx +36 -0
  74. package/src/pages/market/features/SideBar/index.tsx +23 -0
  75. package/src/pages/market/features/TagList/index.tsx +29 -0
  76. package/src/pages/market/index.page.tsx +11 -2
  77. package/src/pages/market/layout.mobile.tsx +8 -7
  78. package/src/pages/market/layout.tsx +39 -1
  79. package/src/pages/settings/features/Header/Desktop.tsx +2 -4
  80. package/src/pages/settings/features/Header/Mobile.tsx +1 -1
  81. package/src/pages/settings/features/Settings/Common.tsx +1 -2
  82. package/src/pages/settings/features/Settings/LLM/index.tsx +12 -1
  83. package/src/pages/settings/features/Settings/index.tsx +17 -4
  84. package/src/services/_header.ts +1 -1
  85. package/src/services/agentMarket.ts +21 -0
  86. package/src/store/global/initialState.ts +1 -2
  87. package/src/store/global/selectors.test.ts +0 -1
  88. package/src/store/market/action.ts +57 -0
  89. package/src/store/market/index.ts +3 -0
  90. package/src/store/market/initialState.ts +17 -0
  91. package/src/store/market/selectors.ts +36 -0
  92. package/src/store/market/store.ts +51 -0
  93. package/src/store/session/slices/session/action.ts +5 -3
  94. package/src/store/session/slices/session/initialState.ts +2 -0
  95. package/src/store/session/slices/session/reducers/session.test.ts +4 -4
  96. package/src/types/market.ts +19 -0
  97. package/src/types/meta.ts +1 -1
  98. package/src/utils/filter.ts +1 -1
  99. package/src/utils/findDuplicates.ts +22 -0
  100. package/scripts/genDefaultLocale.ts +0 -18
  101. package/scripts/genResources.ts +0 -51
  102. package/scripts/toc.ts +0 -7
  103. package/src/components/Empty/index.tsx +0 -63
  104. package/src/components/Empty/style.ts +0 -31
  105. package/src/components/SelectWithImg/index.tsx +0 -97
  106. package/src/components/SelectWithImg/styles.ts +0 -43
  107. package/src/pages/chat/features/Conversation/Input/ActionRight.tsx +0 -31
  108. package/src/pages/chat/features/Conversation/Input/Footer.tsx +0 -49
  109. /package/src/components/{ManifestPreviewer.tsx → ManifestPreviewer/index.tsx} +0 -0
  110. /package/src/pages/chat/features/Conversation/Input/{Token.tsx → ActionBar/Token.tsx} +0 -0
@@ -1,6 +1,7 @@
1
1
  import common from '../../../locales/en_US/common.json';
2
2
  import empty from '../../../locales/en_US/empty.json';
3
3
  import error from '../../../locales/en_US/error.json';
4
+ import market from '../../../locales/en_US/market.json';
4
5
  import plugin from '../../../locales/en_US/plugin.json';
5
6
  import setting from '../../../locales/en_US/setting.json';
6
7
  import welcome from '../../../locales/en_US/welcome.json';
@@ -9,6 +10,7 @@ const resources = {
9
10
  common,
10
11
  empty,
11
12
  error,
13
+ market,
12
14
  plugin,
13
15
  setting,
14
16
  welcome,
@@ -1,6 +1,7 @@
1
1
  import common from '../../../locales/ru_RU/common.json';
2
2
  import empty from '../../../locales/ru_RU/empty.json';
3
3
  import error from '../../../locales/ru_RU/error.json';
4
+ import market from '../../../locales/ru_RU/market.json';
4
5
  import plugin from '../../../locales/ru_RU/plugin.json';
5
6
  import setting from '../../../locales/ru_RU/setting.json';
6
7
  import welcome from '../../../locales/ru_RU/welcome.json';
@@ -9,6 +10,7 @@ const resources = {
9
10
  common,
10
11
  empty,
11
12
  error,
13
+ market,
12
14
  plugin,
13
15
  setting,
14
16
  welcome,
@@ -1,6 +1,7 @@
1
1
  import common from '../default/common';
2
2
  import empty from '../default/empty';
3
3
  import error from '../default/error';
4
+ import market from '../default/market';
4
5
  import plugin from '../default/plugin';
5
6
  import setting from '../default/setting';
6
7
  import welcome from '../default/welcome';
@@ -9,6 +10,7 @@ const resources = {
9
10
  common,
10
11
  empty,
11
12
  error,
13
+ market,
12
14
  plugin,
13
15
  setting,
14
16
  welcome,
@@ -1,7 +1,8 @@
1
1
  import OpenAI from 'openai';
2
2
 
3
+ import { getClientConfig } from '@/config/client';
3
4
  import { getOpenAIAuthFromRequest } from '@/const/fetch';
4
- import { ChatErrorType, ErrorType } from '@/types/fetch';
5
+ import { ErrorType } from '@/types/fetch';
5
6
  import { OpenAIStreamPayload } from '@/types/openai';
6
7
 
7
8
  import { checkAuth } from '../auth';
@@ -24,17 +25,12 @@ export default async function handler(req: Request) {
24
25
  }
25
26
 
26
27
  let openai: OpenAI;
27
- if (useAzure) {
28
- if (!apiVersion) return createErrorResponse(ChatErrorType.BadRequest);
29
28
 
30
- // `https://test-001.openai.azure.com/openai/deployments/gpt-35-turbo`,
31
- const url = `${endpoint}/openai/deployments/${payload.model.replace('.', '')}`;
29
+ const { USE_AZURE_OPENAI } = getClientConfig();
30
+ const useAzureOpenAI = useAzure || USE_AZURE_OPENAI;
32
31
 
33
- openai = createAzureOpenai({
34
- apiVersion,
35
- endpoint: url,
36
- userApiKey: apiKey,
37
- });
32
+ if (useAzureOpenAI) {
33
+ openai = createAzureOpenai({ apiVersion, endpoint, model: payload.model, userApiKey: apiKey });
38
34
  } else {
39
35
  openai = createOpenai(apiKey, endpoint);
40
36
  }
@@ -1,25 +1,29 @@
1
1
  import OpenAI, { ClientOptions } from 'openai';
2
+ import urlJoin from 'url-join';
2
3
 
3
4
  import { getServerConfig } from '@/config/server';
4
5
 
5
6
  // 创建 Azure OpenAI 实例
6
7
  export const createAzureOpenai = (params: {
7
- apiVersion: string;
8
- endpoint: string;
8
+ apiVersion?: string | null;
9
+ endpoint?: string | null;
10
+ model: string;
9
11
  userApiKey?: string | null;
10
12
  }) => {
11
- const { AZURE_API_KEY } = getServerConfig();
13
+ const { OPENAI_PROXY_URL = '', AZURE_API_VERSION, AZURE_API_KEY } = getServerConfig();
12
14
 
13
- const baseURL = params.endpoint;
14
- const apiKey = !params.userApiKey ? AZURE_API_KEY : params.userApiKey;
15
+ const endpoint = !params.endpoint ? OPENAI_PROXY_URL : params.endpoint;
16
+ const baseURL = urlJoin(endpoint, `/openai/deployments/${params.model.replace('.', '')}`); // refs: https://test-001.openai.azure.com/openai/deployments/gpt-35-turbo
17
+
18
+ const defaultApiVersion = AZURE_API_VERSION || '2023-08-01-preview';
19
+ const apiVersion = !params.apiVersion ? defaultApiVersion : params.apiVersion;
20
+ const apiKey = !params.userApiKey ? AZURE_API_KEY ?? '' : params.userApiKey;
15
21
 
16
22
  const config: ClientOptions = {
17
- apiKey: apiKey,
23
+ apiKey,
18
24
  baseURL,
19
25
  defaultHeaders: { 'api-key': apiKey },
20
- defaultQuery: {
21
- 'api-version': params.apiVersion,
22
- },
26
+ defaultQuery: { 'api-version': apiVersion },
23
27
  };
24
28
 
25
29
  return new OpenAI(config);
@@ -9,7 +9,7 @@ import { useSessionStore } from '@/store/session';
9
9
  import { agentSelectors } from '@/store/session/selectors';
10
10
  import { LanguageModel } from '@/types/llm';
11
11
 
12
- const InputActions = memo(() => {
12
+ const ActionLeft = memo(() => {
13
13
  const { t } = useTranslation('setting');
14
14
 
15
15
  const [model, temperature, historyCount, unlimited, updateAgentConfig] = useSessionStore((s) => {
@@ -107,4 +107,4 @@ const InputActions = memo(() => {
107
107
  );
108
108
  });
109
109
 
110
- export default InputActions;
110
+ export default ActionLeft;
@@ -0,0 +1,36 @@
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import { Popconfirm } from 'antd';
3
+ import { Eraser } from 'lucide-react';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { useSessionStore } from '@/store/session';
8
+
9
+ import SaveTopic from '../Footer/SaveTopic';
10
+
11
+ const ActionsRight = memo(() => {
12
+ const { t } = useTranslation('setting');
13
+ const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig]);
14
+
15
+ return (
16
+ <>
17
+ <SaveTopic />
18
+ <Popconfirm
19
+ cancelText={t('cancel', { ns: 'common' })}
20
+ okButtonProps={{ danger: true }}
21
+ okText={t('ok', { ns: 'common' })}
22
+ onConfirm={() => clearMessage()}
23
+ placement={'topRight'}
24
+ title={t('confirmClearCurrentMessages', { ns: 'common' })}
25
+ >
26
+ <ActionIcon
27
+ icon={Eraser}
28
+ placement={'bottom'}
29
+ title={t('clearCurrentMessages', { ns: 'common' })}
30
+ />
31
+ </Popconfirm>
32
+ </>
33
+ );
34
+ });
35
+
36
+ export default ActionsRight;
@@ -0,0 +1,40 @@
1
+ import { DraggablePanel } from '@lobehub/ui';
2
+ import { ReactNode, memo } from 'react';
3
+
4
+ import { CHAT_TEXTAREA_HEIGHT, HEADER_HEIGHT } from '@/const/layoutTokens';
5
+ import { useGlobalStore } from '@/store/global';
6
+
7
+ interface ChatInputDesktopLayoutProps {
8
+ children: ReactNode;
9
+ expand?: boolean;
10
+ }
11
+
12
+ const ChatInputDesktopLayout = memo<ChatInputDesktopLayoutProps>(({ children, expand }) => {
13
+ const [inputHeight, updatePreference] = useGlobalStore((s) => [
14
+ s.preference.inputHeight,
15
+ s.updatePreference,
16
+ ]);
17
+
18
+ return (
19
+ <DraggablePanel
20
+ expandable={false}
21
+ fullscreen={expand}
22
+ headerHeight={HEADER_HEIGHT}
23
+ minHeight={CHAT_TEXTAREA_HEIGHT}
24
+ onSizeChange={(_, size) => {
25
+ if (!size) return;
26
+
27
+ updatePreference({
28
+ inputHeight: typeof size.height === 'string' ? Number.parseInt(size.height) : size.height,
29
+ });
30
+ }}
31
+ placement="bottom"
32
+ size={{ height: inputHeight, width: '100%' }}
33
+ style={{ zIndex: 10 }}
34
+ >
35
+ {children}
36
+ </DraggablePanel>
37
+ );
38
+ });
39
+
40
+ export default ChatInputDesktopLayout;
@@ -0,0 +1,27 @@
1
+ import { ActionIcon, Icon, Tooltip } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { useResponsive } from 'antd-style';
4
+ import { LucideGalleryVerticalEnd } from 'lucide-react';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import { useSessionStore } from '@/store/session';
9
+
10
+ const SaveTopic = memo(() => {
11
+ const { t } = useTranslation('common');
12
+ const [hasTopic, saveToTopic] = useSessionStore((s) => [!!s.activeTopicId, s.saveToTopic]);
13
+ const { mobile } = useResponsive();
14
+
15
+ if (hasTopic) return undefined;
16
+
17
+ const Render = mobile ? ActionIcon : Button;
18
+ const icon: any = mobile ? LucideGalleryVerticalEnd : <Icon icon={LucideGalleryVerticalEnd} />;
19
+
20
+ return (
21
+ <Tooltip title={t('topic.saveCurrentMessages')}>
22
+ <Render icon={icon} onClick={saveToTopic} />
23
+ </Tooltip>
24
+ );
25
+ });
26
+
27
+ export default SaveTopic;
@@ -0,0 +1,35 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { useTheme } from 'antd-style';
3
+ import { ArrowBigUp, CornerDownLeft } from 'lucide-react';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import SaveTopic from './SaveTopic';
9
+
10
+ const Footer = memo(() => {
11
+ const theme = useTheme();
12
+ const { t } = useTranslation('common');
13
+
14
+ return (
15
+ <>
16
+ <Flexbox
17
+ gap={4}
18
+ horizontal
19
+ style={{ color: theme.colorTextDescription, fontSize: 12, marginRight: 12 }}
20
+ >
21
+ <Icon icon={CornerDownLeft} />
22
+ <span>{t('send')}</span>
23
+ <span>/</span>
24
+ <Flexbox horizontal>
25
+ <Icon icon={ArrowBigUp} />
26
+ <Icon icon={CornerDownLeft} />
27
+ </Flexbox>
28
+ <span>{t('warp')}</span>
29
+ </Flexbox>
30
+ <SaveTopic />
31
+ </>
32
+ );
33
+ });
34
+
35
+ export default Footer;
@@ -0,0 +1,12 @@
1
+ import { ReactNode, memo } from 'react';
2
+
3
+ interface ChatInputMobileLayoutProps {
4
+ children: ReactNode;
5
+ expand?: boolean;
6
+ }
7
+
8
+ const ChatInputMobileLayout = memo<ChatInputMobileLayoutProps>(({ children }) => {
9
+ return <div>{children}</div>;
10
+ });
11
+
12
+ export default ChatInputMobileLayout;
@@ -1,64 +1,46 @@
1
- import { ChatInputArea, DraggablePanel } from '@lobehub/ui';
1
+ import { ChatInputArea } from '@lobehub/ui';
2
+ import { useResponsive } from 'antd-style';
2
3
  import { memo, useState } from 'react';
3
4
  import { useTranslation } from 'react-i18next';
4
5
 
5
- import { CHAT_TEXTAREA_HEIGHT, HEADER_HEIGHT } from '@/const/layoutTokens';
6
- import { useGlobalStore } from '@/store/global';
6
+ import { CHAT_TEXTAREA_HEIGHT } from '@/const/layoutTokens';
7
7
  import { useSessionStore } from '@/store/session';
8
8
 
9
- import InputActions from './Action';
10
- import ActionsRight from './ActionRight';
9
+ import ActionLeft from './ActionBar/ActionLeft';
10
+ import ActionsRight from './ActionBar/ActionRight';
11
+ import Token from './ActionBar/Token';
12
+ import Desktop from './Desktop';
11
13
  import Footer from './Footer';
12
- import Token from './Token';
14
+ import Mobile from './Mobile';
13
15
 
14
16
  const ChatInput = () => {
15
17
  const { t } = useTranslation('common');
16
18
  const [expand, setExpand] = useState<boolean>(false);
17
19
  const [message, setMessage] = useState('');
20
+ const { mobile } = useResponsive();
18
21
 
19
- const [inputHeight, updatePreference] = useGlobalStore((s) => [
20
- s.preference.inputHeight,
21
- s.updatePreference,
22
+ const [isLoading, sendMessage, stopGenerateMessage] = useSessionStore((s) => [
23
+ !!s.chatLoadingId,
24
+ s.sendMessage,
25
+ s.stopGenerateMessage,
22
26
  ]);
23
- const [isLoading, hasTopic, sendMessage, saveToTopic, stopGenerateMessage] = useSessionStore(
24
- (s) => [
25
- !!s.chatLoadingId,
26
- !!s.activeTopicId,
27
- s.sendMessage,
28
- s.saveToTopic,
29
- s.stopGenerateMessage,
30
- ],
31
- );
32
27
 
33
- return (
34
- <DraggablePanel
35
- expandable={false}
36
- fullscreen={expand}
37
- headerHeight={HEADER_HEIGHT}
38
- minHeight={CHAT_TEXTAREA_HEIGHT}
39
- onSizeChange={(_, size) => {
40
- if (!size) return;
28
+ const Render = mobile ? Mobile : Desktop;
41
29
 
42
- updatePreference({
43
- inputHeight: typeof size.height === 'string' ? Number.parseInt(size.height) : size.height,
44
- });
45
- }}
46
- placement="bottom"
47
- size={{ height: inputHeight, width: '100%' }}
48
- style={{ zIndex: 10 }}
49
- >
30
+ return (
31
+ <Render expand={expand}>
50
32
  <ChatInputArea
51
33
  actions={
52
34
  <>
53
- <InputActions />
35
+ <ActionLeft />
54
36
  <Token input={message} />
55
37
  </>
56
38
  }
57
39
  actionsRight={<ActionsRight />}
58
40
  expand={expand}
59
- footer={<Footer hasTopic={hasTopic} saveToTopic={saveToTopic} />}
41
+ footer={<Footer />}
60
42
  loading={isLoading}
61
- minHeight={CHAT_TEXTAREA_HEIGHT}
43
+ minHeight={mobile ? 0 : CHAT_TEXTAREA_HEIGHT}
62
44
  onExpandChange={setExpand}
63
45
  onInputChange={setMessage}
64
46
  onSend={sendMessage}
@@ -70,7 +52,7 @@ const ChatInput = () => {
70
52
  }}
71
53
  value={message}
72
54
  />
73
- </DraggablePanel>
55
+ </Render>
74
56
  );
75
57
  };
76
58
 
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Logo, SearchBar } from '@lobehub/ui';
1
+ import { ActionIcon, Logo } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { MessageSquarePlus } from 'lucide-react';
4
4
  import Link from 'next/link';
@@ -8,6 +8,8 @@ import { Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import { useSessionStore } from '@/store/session';
10
10
 
11
+ import SessionSearchBar from '../../SessionSearchBar';
12
+
11
13
  export const useStyles = createStyles(({ css, token }) => ({
12
14
  logo: css`
13
15
  fill: ${token.colorText};
@@ -21,7 +23,7 @@ export const useStyles = createStyles(({ css, token }) => ({
21
23
  const Header = memo(() => {
22
24
  const { styles } = useStyles();
23
25
  const { t } = useTranslation('common');
24
- const [keywords, createSession] = useSessionStore((s) => [s.searchKeywords, s.createSession]);
26
+ const [createSession] = useSessionStore((s) => [s.createSession]);
25
27
 
26
28
  return (
27
29
  <Flexbox className={styles.top} gap={16} padding={16}>
@@ -31,22 +33,13 @@ const Header = memo(() => {
31
33
  </Link>
32
34
  <ActionIcon
33
35
  icon={MessageSquarePlus}
34
- onClick={createSession}
36
+ onClick={() => createSession()}
35
37
  size={{ fontSize: 24 }}
36
38
  style={{ flex: 'none' }}
37
39
  title={t('newAgent')}
38
40
  />
39
41
  </Flexbox>
40
- <SearchBar
41
- allowClear
42
- enableShortKey
43
- onChange={(e) => useSessionStore.setState({ searchKeywords: e.target.value })}
44
- placeholder={t('searchAgentPlaceholder')}
45
- shortKey={'k'}
46
- spotlight
47
- type={'ghost'}
48
- value={keywords}
49
- />
42
+ <SessionSearchBar />
50
43
  </Flexbox>
51
44
  );
52
45
  });
@@ -26,7 +26,7 @@ const Header = memo(() => {
26
26
  left={<AvatarWithUpload size={28} style={{ marginLeft: 8 }} />}
27
27
  right={
28
28
  <>
29
- <ActionIcon icon={MessageSquarePlus} onClick={createSession} />
29
+ <ActionIcon icon={MessageSquarePlus} onClick={() => createSession()} />
30
30
  <ActionIcon
31
31
  icon={Settings2}
32
32
  onClick={() => {
@@ -12,7 +12,7 @@ const AddButton = memo<ButtonProps>((props) => {
12
12
  const createSession = useSessionStore((s) => s.createSession);
13
13
  return (
14
14
  <Flexbox style={{ margin: '12px 16px' }}>
15
- <Button block icon={<Icon icon={Plus} />} onClick={createSession} {...props}>
15
+ <Button block icon={<Icon icon={Plus} />} onClick={() => createSession()} {...props}>
16
16
  {t('newAgent')}
17
17
  </Button>
18
18
  </Flexbox>
@@ -1,7 +1,7 @@
1
1
  import { SiOpenai } from '@icons-pack/react-simple-icons';
2
2
  import { Avatar, List, Tag } from '@lobehub/ui';
3
3
  import { useHover } from 'ahooks';
4
- import { createStyles } from 'antd-style';
4
+ import { createStyles, useResponsive } from 'antd-style';
5
5
  import { memo, useMemo, useRef, useState } from 'react';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
  import { shallow } from 'zustand/shallow';
@@ -34,7 +34,7 @@ const SessionItem = memo<SessionItemProps>(({ id }) => {
34
34
  const [open, setOpen] = useState(false);
35
35
  const ref = useRef(null);
36
36
  const isHovering = useHover(ref);
37
-
37
+ const { mobile } = useResponsive();
38
38
  const { styles } = useStyles();
39
39
  const [defaultModel] = useGlobalStore((s) => [globalSelectors.defaultAgentConfig(s).model]);
40
40
 
@@ -98,7 +98,8 @@ const SessionItem = memo<SessionItemProps>(({ id }) => {
98
98
  return (
99
99
  <Item
100
100
  actions={actions}
101
- active={active}
101
+ // needn't active state in mobile
102
+ active={mobile ? false : active}
102
103
  addon={addon}
103
104
  avatar={avatarRender}
104
105
  className={styles.container}
@@ -26,10 +26,8 @@ const SessionList = memo(() => {
26
26
  <Link
27
27
  href={SESSION_CHAT_URL(id, mobile)}
28
28
  key={id}
29
- // replace onClick with onPointerDown to prevent double click on mobile
30
- onPointerDown={(e) => {
29
+ onClick={(e) => {
31
30
  e.preventDefault();
32
-
33
31
  if (mobile) switchSession(id);
34
32
  else activeSession(id);
35
33
  }}
@@ -1,22 +1,12 @@
1
- import { SearchBar } from '@lobehub/ui';
2
1
  import { ReactNode, memo } from 'react';
3
- import { useTranslation } from 'react-i18next';
4
2
 
5
- import { useSessionStore } from '@/store/session';
3
+ import SessionSearchBar from '../SessionSearchBar';
6
4
 
7
5
  const Sessions = memo<{ children: ReactNode }>(({ children }) => {
8
- const { t } = useTranslation('common');
9
- const [keywords] = useSessionStore((s) => [s.searchKeywords, s.createSession]);
10
6
  return (
11
7
  <>
12
8
  <div style={{ padding: '8px 16px' }}>
13
- <SearchBar
14
- allowClear
15
- onChange={(e) => useSessionStore.setState({ searchKeywords: e.target.value })}
16
- placeholder={t('searchAgentPlaceholder')}
17
- type={'block'}
18
- value={keywords}
19
- />
9
+ <SessionSearchBar />
20
10
  </div>
21
11
  {children}
22
12
  </>
@@ -0,0 +1,26 @@
1
+ import { SearchBar } from '@lobehub/ui';
2
+ import { useResponsive } from 'antd-style';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { useSessionStore } from '@/store/session';
7
+
8
+ const SessionSearchBar = memo(() => {
9
+ const { t } = useTranslation('common');
10
+ const [keywords] = useSessionStore((s) => [s.searchKeywords]);
11
+ const { mobile } = useResponsive();
12
+ return (
13
+ <SearchBar
14
+ allowClear
15
+ enableShortKey={!mobile}
16
+ onChange={(e) => useSessionStore.setState({ searchKeywords: e.target.value })}
17
+ placeholder={t('searchAgentPlaceholder')}
18
+ shortKey={'k'}
19
+ spotlight={!mobile}
20
+ type={mobile ? 'block' : 'ghost'}
21
+ value={keywords}
22
+ />
23
+ );
24
+ });
25
+
26
+ export default SessionSearchBar;
@@ -1,9 +1,10 @@
1
+ import { EmptyCard } from '@lobehub/ui';
1
2
  import { useThemeMode } from 'antd-style';
2
3
  import isEqual from 'fast-deep-equal';
4
+ import { useMemo } from 'react';
3
5
  import { useTranslation } from 'react-i18next';
4
6
  import { Flexbox } from 'react-layout-kit';
5
7
 
6
- import Empty from '@/components/Empty';
7
8
  import { useGlobalStore } from '@/store/global';
8
9
  import { useSessionChatInit, useSessionStore } from '@/store/session';
9
10
  import { topicSelectors } from '@/store/session/selectors';
@@ -15,20 +16,27 @@ export const Topic = () => {
15
16
  const init = useSessionChatInit();
16
17
  const topics = useSessionStore(topicSelectors.currentTopics, isEqual);
17
18
  const { isDarkMode } = useThemeMode();
18
- const [activeTopicId] = useSessionStore((s) => [s.activeTopicId]);
19
+ const [activeTopicId, keywords] = useSessionStore((s) => [
20
+ s.activeTopicId,
21
+ s.topicSearchKeywords,
22
+ ]);
19
23
  const { t } = useTranslation('empty');
20
-
21
24
  const [visible, updateGuideState] = useGlobalStore((s) => [
22
25
  s.preference.guide?.topic,
23
26
  s.updateGuideState,
24
27
  ]);
25
28
 
29
+ const topicsData = useMemo(() => {
30
+ if (!keywords) return topics;
31
+ return topics.filter(({ title }) => title.toLowerCase().includes(keywords.toLowerCase()));
32
+ }, [topics, keywords]);
33
+
26
34
  return !init ? (
27
35
  <SkeletonList />
28
36
  ) : (
29
37
  <Flexbox gap={2} style={{ marginBottom: 12 }}>
30
38
  {topics?.length === 0 && (
31
- <Empty
39
+ <EmptyCard
32
40
  alt={t('topic.desc')}
33
41
  cover={`/images/empty_topic_${isDarkMode ? 'dark' : 'light'}.webp`}
34
42
  desc={t('topic.desc')}
@@ -43,9 +51,13 @@ export const Topic = () => {
43
51
  />
44
52
  )}
45
53
 
46
- <TopicItem active={!activeTopicId} fav={false} title={'默认话题'} />
54
+ <TopicItem
55
+ active={!activeTopicId}
56
+ fav={false}
57
+ title={t('topic.defaultTitle', { ns: 'common' })}
58
+ />
47
59
 
48
- {topics.map(({ id, favorite, title }) => (
60
+ {topicsData.map(({ id, favorite, title }) => (
49
61
  <TopicItem active={activeTopicId === id} fav={favorite} id={id} key={id} title={title} />
50
62
  ))}
51
63
  </Flexbox>
@@ -0,0 +1,24 @@
1
+ import { SearchBar } from '@lobehub/ui';
2
+ import { useResponsive } from 'antd-style';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { useSessionStore } from '@/store/session';
7
+
8
+ const TopicSearchBar = memo(() => {
9
+ const { t } = useTranslation('common');
10
+ const [keywords] = useSessionStore((s) => [s.topicSearchKeywords]);
11
+ const { mobile } = useResponsive();
12
+ return (
13
+ <SearchBar
14
+ allowClear
15
+ onChange={(e) => useSessionStore.setState({ topicSearchKeywords: e.target.value })}
16
+ placeholder={t('topic.searchPlaceholder')}
17
+ spotlight={!mobile}
18
+ type={mobile ? 'block' : 'ghost'}
19
+ value={keywords}
20
+ />
21
+ );
22
+ });
23
+
24
+ export default TopicSearchBar;
@@ -1,29 +1,22 @@
1
- import { SearchBar } from '@lobehub/ui';
2
1
  import { useResponsive } from 'antd-style';
3
2
  import { memo } from 'react';
4
- import { useTranslation } from 'react-i18next';
5
3
  import { Flexbox } from 'react-layout-kit';
6
4
 
7
5
  import Desktop from './Desktop';
8
6
  import Mobile from './Mobile';
9
7
  import { Topic } from './Topic';
8
+ import TopicSearchBar from './TopicSearchBar';
10
9
 
11
10
  const SideBar = memo(() => {
12
11
  const { mobile } = useResponsive();
13
12
 
14
13
  const Render = mobile ? Mobile : Desktop;
15
14
 
16
- const { t } = useTranslation('common');
17
-
18
15
  return (
19
16
  <Render>
20
17
  <Flexbox height={'100%'} style={{ overflow: 'hidden' }}>
21
18
  <Flexbox padding={16}>
22
- <SearchBar
23
- placeholder={t('topic.searchPlaceholder')}
24
- spotlight={!mobile}
25
- type={mobile ? 'block' : 'ghost'}
26
- />
19
+ <TopicSearchBar />
27
20
  </Flexbox>
28
21
  <Flexbox gap={16} paddingInline={16} style={{ overflowY: 'auto', position: 'relative' }}>
29
22
  <Topic />