@lobehub/chat 0.151.0 → 0.151.1

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 (50) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/locales/ar/chat.json +2 -3
  3. package/locales/ar/welcome.json +34 -0
  4. package/locales/bg-BG/chat.json +2 -3
  5. package/locales/bg-BG/welcome.json +34 -0
  6. package/locales/de-DE/chat.json +0 -1
  7. package/locales/de-DE/welcome.json +34 -0
  8. package/locales/en-US/chat.json +0 -1
  9. package/locales/en-US/welcome.json +34 -0
  10. package/locales/es-ES/chat.json +0 -1
  11. package/locales/es-ES/welcome.json +34 -0
  12. package/locales/fr-FR/chat.json +0 -1
  13. package/locales/fr-FR/welcome.json +34 -0
  14. package/locales/it-IT/chat.json +0 -1
  15. package/locales/it-IT/welcome.json +34 -0
  16. package/locales/ja-JP/chat.json +0 -1
  17. package/locales/ja-JP/welcome.json +35 -1
  18. package/locales/ko-KR/chat.json +0 -1
  19. package/locales/ko-KR/welcome.json +34 -0
  20. package/locales/nl-NL/chat.json +3 -4
  21. package/locales/nl-NL/welcome.json +34 -0
  22. package/locales/pl-PL/chat.json +2 -3
  23. package/locales/pl-PL/welcome.json +34 -0
  24. package/locales/pt-BR/chat.json +0 -1
  25. package/locales/pt-BR/welcome.json +34 -0
  26. package/locales/ru-RU/chat.json +0 -1
  27. package/locales/ru-RU/welcome.json +34 -0
  28. package/locales/tr-TR/chat.json +1 -2
  29. package/locales/tr-TR/welcome.json +34 -0
  30. package/locales/vi-VN/chat.json +0 -1
  31. package/locales/vi-VN/welcome.json +34 -0
  32. package/locales/zh-CN/chat.json +0 -1
  33. package/locales/zh-CN/welcome.json +34 -0
  34. package/locales/zh-TW/chat.json +0 -1
  35. package/locales/zh-TW/welcome.json +34 -0
  36. package/package.json +1 -1
  37. package/src/app/chat/features/Migration/Failed.tsx +2 -1
  38. package/src/config/client.ts +0 -5
  39. package/src/const/guide.ts +86 -0
  40. package/src/const/url.ts +15 -8
  41. package/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx +108 -0
  42. package/src/features/Conversation/components/InboxWelcome/QuestionSuggest.tsx +99 -0
  43. package/src/features/Conversation/components/InboxWelcome/index.tsx +76 -0
  44. package/src/features/Conversation/components/VirtualizedList/index.tsx +11 -4
  45. package/src/layout/GlobalProvider/AppTheme.tsx +2 -1
  46. package/src/locales/default/chat.ts +0 -2
  47. package/src/locales/default/welcome.ts +35 -0
  48. package/src/services/chat.ts +44 -25
  49. package/src/store/chat/slices/message/selectors.test.ts +27 -1
  50. package/src/store/chat/slices/message/selectors.ts +11 -1
@@ -0,0 +1,99 @@
1
+ 'use client';
2
+
3
+ import { ActionIcon } from '@lobehub/ui';
4
+ import { createStyles } from 'antd-style';
5
+ import { shuffle } from 'lodash-es';
6
+ import { ArrowRight } from 'lucide-react';
7
+ import Link from 'next/link';
8
+ import { memo } from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+ import { Flexbox } from 'react-layout-kit';
11
+
12
+ import { USAGE_DOCUMENTS } from '@/const/url';
13
+ import { useChatInput } from '@/features/ChatInput/useChatInput';
14
+
15
+ const useStyles = createStyles(({ css, token }) => ({
16
+ card: css`
17
+ cursor: pointer;
18
+
19
+ padding: 12px 24px;
20
+
21
+ color: ${token.colorText};
22
+
23
+ background: ${token.colorBgContainer};
24
+ border-radius: 48px;
25
+
26
+ &:hover {
27
+ background: ${token.colorBgElevated};
28
+ }
29
+ `,
30
+ icon: css`
31
+ color: ${token.colorTextSecondary};
32
+ `,
33
+ title: css`
34
+ color: ${token.colorTextDescription};
35
+ `,
36
+ }));
37
+
38
+ const qa = shuffle([
39
+ 'q01',
40
+ 'q02',
41
+ 'q03',
42
+ 'q04',
43
+ 'q05',
44
+ 'q06',
45
+ 'q07',
46
+ 'q08',
47
+ 'q09',
48
+ 'q10',
49
+ 'q11',
50
+ 'q12',
51
+ 'q13',
52
+ 'q14',
53
+ 'q15',
54
+ ]).slice(0, 5);
55
+
56
+ const QuestionSuggest = memo(() => {
57
+ const { onInput, onSend } = useChatInput();
58
+ const { t } = useTranslation('welcome');
59
+ const { styles } = useStyles();
60
+
61
+ const handoleSend = (qa: string) => {
62
+ onInput(qa);
63
+ onSend();
64
+ };
65
+
66
+ return (
67
+ <Flexbox gap={8} width={'100%'}>
68
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
69
+ <div className={styles.title}>{t('guide.questions.title')}</div>
70
+ <Link href={USAGE_DOCUMENTS} target={'_blank'}>
71
+ <ActionIcon
72
+ icon={ArrowRight}
73
+ size={{ blockSize: 24, fontSize: 16 }}
74
+ title={t('guide.questions.moreBtn')}
75
+ />
76
+ </Link>
77
+ </Flexbox>
78
+ <Flexbox gap={8} horizontal wrap={'wrap'}>
79
+ {qa.map((item) => {
80
+ const text = t(`guide.qa.${item}` as any);
81
+ return (
82
+ <Flexbox
83
+ align={'center'}
84
+ className={styles.card}
85
+ gap={8}
86
+ horizontal
87
+ key={item}
88
+ onClick={() => handoleSend(text)}
89
+ >
90
+ {t(text)}
91
+ </Flexbox>
92
+ );
93
+ })}
94
+ </Flexbox>
95
+ </Flexbox>
96
+ );
97
+ });
98
+
99
+ export default QuestionSuggest;
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ import { FluentEmoji, Markdown } from '@lobehub/ui';
4
+ import { createStyles } from 'antd-style';
5
+ import { memo, useEffect, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Center, Flexbox } from 'react-layout-kit';
8
+
9
+ import AgentsSuggest from './AgentsSuggest';
10
+ import QuestionSuggest from './QuestionSuggest';
11
+
12
+ const useStyles = createStyles(({ css, responsive }) => ({
13
+ container: css`
14
+ align-items: center;
15
+ ${responsive.mobile} {
16
+ align-items: flex-start;
17
+ }
18
+ `,
19
+ desc: css`
20
+ font-size: 14px;
21
+ text-align: center;
22
+ ${responsive.mobile} {
23
+ text-align: left;
24
+ }
25
+ `,
26
+ title: css`
27
+ margin-top: 0.2em;
28
+ margin-bottom: 0;
29
+
30
+ font-size: 32px;
31
+ font-weight: bolder;
32
+ line-height: 1;
33
+ ${responsive.mobile} {
34
+ font-size: 24px;
35
+ }
36
+ `,
37
+ }));
38
+
39
+ const InboxWelcome = memo(() => {
40
+ const { t } = useTranslation('welcome');
41
+ const [greeting, setGreeting] = useState<'morning' | 'noon' | 'afternoon' | 'night'>();
42
+ const { styles } = useStyles();
43
+
44
+ useEffect(() => {
45
+ const now = new Date();
46
+ const hours = now.getHours();
47
+
48
+ if (hours >= 4 && hours < 11) {
49
+ setGreeting('morning');
50
+ } else if (hours >= 11 && hours < 14) {
51
+ setGreeting('noon');
52
+ } else if (hours >= 14 && hours < 18) {
53
+ setGreeting('afternoon');
54
+ } else {
55
+ setGreeting('night');
56
+ }
57
+ }, []);
58
+
59
+ return (
60
+ <Center padding={16} width={'100%'}>
61
+ <Flexbox className={styles.container} gap={16} style={{ maxWidth: 800 }} width={'100%'}>
62
+ <Flexbox align={'center'} gap={8} horizontal>
63
+ <FluentEmoji emoji={'👋'} size={40} type={'anim'} />
64
+ <h1 className={styles.title}>{greeting && t(`guide.welcome.${greeting}`)}</h1>
65
+ </Flexbox>
66
+ <Markdown className={styles.desc} variant={'chat'}>
67
+ {t('guide.defaultMessage')}
68
+ </Markdown>
69
+ <AgentsSuggest />
70
+ <QuestionSuggest />
71
+ </Flexbox>
72
+ </Center>
73
+ );
74
+ });
75
+
76
+ export default InboxWelcome;
@@ -9,10 +9,15 @@ import { isMobileScreen } from '@/utils/screen';
9
9
 
10
10
  import AutoScroll from '../AutoScroll';
11
11
  import Item from '../ChatItem';
12
+ import InboxWelcome from '../InboxWelcome';
13
+
14
+ const WELCOME_ID = 'welcome';
12
15
 
13
16
  const itemContent = (index: number, id: string) => {
14
17
  const isMobile = isMobileScreen();
15
18
 
19
+ if (id === WELCOME_ID) return <InboxWelcome />;
20
+
16
21
  return index === 0 ? (
17
22
  <div style={{ height: 24 + (isMobile ? 0 : 64) }} />
18
23
  ) : (
@@ -27,15 +32,17 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile }) => {
27
32
  const virtuosoRef = useRef<VirtuosoHandle>(null);
28
33
  const [atBottom, setAtBottom] = useState(true);
29
34
 
30
- const data = useChatStore(
31
- (s) => ['empty', ...chatSelectors.currentChatIDsWithGuideMessage(s)],
32
- isEqual,
33
- );
34
35
  const [id, chatLoading] = useChatStore((s) => [
35
36
  chatSelectors.currentChatKey(s),
36
37
  chatSelectors.currentChatLoadingState(s),
37
38
  ]);
38
39
 
40
+ const data = useChatStore((s) => {
41
+ const showInboxWelcome = chatSelectors.showInboxWelcome(s);
42
+ const ids = showInboxWelcome ? [WELCOME_ID] : chatSelectors.currentChatIDsWithGuideMessage(s);
43
+ return ['empty', ...ids];
44
+ }, isEqual);
45
+
39
46
  useEffect(() => {
40
47
  if (virtuosoRef.current) {
41
48
  virtuosoRef.current.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
@@ -5,6 +5,7 @@ import { App } from 'antd';
5
5
  import { ThemeAppearance, createStyles } from 'antd-style';
6
6
  import 'antd/dist/reset.css';
7
7
  import Image from 'next/image';
8
+ import Link from 'next/link';
8
9
  import { PropsWithChildren, ReactNode, memo, useEffect } from 'react';
9
10
 
10
11
  import AntdStaticMethods from '@/components/AntdStaticMethods';
@@ -112,7 +113,7 @@ const AppTheme = memo<AppThemeProps>(
112
113
  >
113
114
  <GlobalStyle />
114
115
  <AntdStaticMethods />
115
- <ConfigProvider config={{ imgAs: Image, imgUnoptimized: true }}>
116
+ <ConfigProvider config={{ aAs: Link, imgAs: Image, imgUnoptimized: true }}>
116
117
  <Container>{children}</Container>
117
118
  </ConfigProvider>
118
119
  </ThemeProvider>
@@ -22,8 +22,6 @@ export default {
22
22
  emptyAgent: '暂无助手',
23
23
  historyRange: '历史范围',
24
24
  inbox: {
25
- defaultMessage:
26
- '你好,我是你的智能助手,你可以问我任何问题,我会尽力回答你。如果需要获得更加专业或定制的助手,可以点击`+`创建自定义助手',
27
25
  desc: '开启大脑集群,激发思维火花。你的智能助理,在这里与你交流一切',
28
26
  title: '随便聊聊',
29
27
  },
@@ -4,6 +4,41 @@ export default {
4
4
  market: '逛逛市场',
5
5
  start: '立即开始',
6
6
  },
7
+ guide: {
8
+ agents: {
9
+ replaceBtn: '换一批',
10
+ title: '新增助理推荐:',
11
+ },
12
+ defaultMessage:
13
+ '我是 LobeChat 你的私人智能助理,我今天能帮你做什么?\n如果需要获得更加专业或定制的助手,可以点击 `+` 创建自定义助手',
14
+ qa: {
15
+ q01: 'LobeHub 是什么?',
16
+ q02: 'LobeChat 是什么?',
17
+ q03: 'LobeChat 是否有社区支持?',
18
+ q04: 'LobeChat 支持哪些功能?',
19
+ q05: 'LobeChat 如何部署和使用?',
20
+ q06: 'LobeChat 的定价是如何的?',
21
+ q07: 'LobeChat 是否免费?',
22
+ q08: '是否有云端服务版?',
23
+ q09: '是否支持本地语言模型?',
24
+ q10: '是否支持图像识别和生成?',
25
+ q11: '是否支持语音合成和语音识别?',
26
+ q12: '是否支持插件系统?',
27
+ q13: '是否有自己的市场来获取 GPTs?',
28
+ q14: '是否支持多种 AI 服务提供商?',
29
+ q15: '我在使用时遇到问题应该怎么办?',
30
+ },
31
+ questions: {
32
+ moreBtn: '了解更多',
33
+ title: '大家都在问:',
34
+ },
35
+ welcome: {
36
+ afternoon: '下午好',
37
+ morning: '早上好',
38
+ night: '晚上好',
39
+ noon: '中午好',
40
+ },
41
+ },
7
42
  header: '欢迎使用',
8
43
  pickAgent: '或从下列助手模板选择',
9
44
  skip: '跳过创建',
@@ -3,6 +3,8 @@ import { produce } from 'immer';
3
3
  import { merge } from 'lodash-es';
4
4
 
5
5
  import { createErrorResponse } from '@/app/api/errorResponse';
6
+ import { INBOX_GUIDE_SYSTEMROLE } from '@/const/guide';
7
+ import { INBOX_SESSION_ID } from '@/const/session';
6
8
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
7
9
  import { TracePayload, TraceTagMap } from '@/const/trace';
8
10
  import { AgentRuntime, ChatCompletionErrorPayload, ModelProvider } from '@/libs/agent-runtime';
@@ -183,11 +185,14 @@ class ChatService {
183
185
  );
184
186
  // ============ 1. preprocess messages ============ //
185
187
 
186
- const oaiMessages = this.processMessages({
187
- messages,
188
- model: payload.model,
189
- tools: enabledPlugins,
190
- });
188
+ const oaiMessages = this.processMessages(
189
+ {
190
+ messages,
191
+ model: payload.model,
192
+ tools: enabledPlugins,
193
+ },
194
+ options,
195
+ );
191
196
 
192
197
  // ============ 2. preprocess tools ============ //
193
198
 
@@ -371,15 +376,18 @@ class ChatService {
371
376
  return await data?.text();
372
377
  };
373
378
 
374
- private processMessages = ({
375
- messages,
376
- tools,
377
- model,
378
- }: {
379
- messages: ChatMessage[];
380
- model: string;
381
- tools?: string[];
382
- }): OpenAIChatMessage[] => {
379
+ private processMessages = (
380
+ {
381
+ messages,
382
+ tools,
383
+ model,
384
+ }: {
385
+ messages: ChatMessage[];
386
+ model: string;
387
+ tools?: string[];
388
+ },
389
+ options?: FetchOptions,
390
+ ): OpenAIChatMessage[] => {
383
391
  // handle content type for vision model
384
392
  // for the models with visual ability, add image url to content
385
393
  // refs: https://platform.openai.com/docs/guides/vision/quick-start
@@ -424,22 +432,33 @@ class ChatService {
424
432
  });
425
433
 
426
434
  return produce(postMessages, (draft) => {
427
- if (!tools || tools.length === 0) return;
428
- const hasFC = modelProviderSelectors.isModelEnabledFunctionCall(model)(
429
- useUserStore.getState(),
430
- );
431
- if (!hasFC) return;
435
+ // Inject InboxGuide SystemRole
436
+ const inboxGuideSystemRole =
437
+ options?.trace?.sessionId === INBOX_SESSION_ID && INBOX_GUIDE_SYSTEMROLE;
432
438
 
433
- const systemMessage = draft.find((i) => i.role === 'system');
439
+ // Inject Tool SystemRole
440
+ const hasTools = tools && tools?.length > 0;
441
+ const hasFC =
442
+ hasTools &&
443
+ modelProviderSelectors.isModelEnabledFunctionCall(model)(useUserStore.getState());
444
+ const toolsSystemRoles =
445
+ hasFC && toolSelectors.enabledSystemRoles(tools)(useToolStore.getState());
434
446
 
435
- const toolsSystemRoles = toolSelectors.enabledSystemRoles(tools)(useToolStore.getState());
436
- if (!toolsSystemRoles) return;
447
+ const injectSystemRoles = [inboxGuideSystemRole, toolsSystemRoles]
448
+ .filter(Boolean)
449
+ .join('\n\n');
450
+
451
+ if (!injectSystemRoles) return;
452
+
453
+ const systemMessage = draft.find((i) => i.role === 'system');
437
454
 
438
455
  if (systemMessage) {
439
- systemMessage.content = systemMessage.content + '\n\n' + toolsSystemRoles;
456
+ systemMessage.content = [systemMessage.content, injectSystemRoles]
457
+ .filter(Boolean)
458
+ .join('\n\n');
440
459
  } else {
441
460
  draft.unshift({
442
- content: toolsSystemRoles,
461
+ content: injectSystemRoles,
443
462
  role: 'system',
444
463
  });
445
464
  }
@@ -451,7 +470,7 @@ class ChatService {
451
470
 
452
471
  const enabled = preferenceSelectors.userAllowTrace(useUserStore.getState());
453
472
 
454
- if (!enabled) return { enabled: false };
473
+ if (!enabled) return { ...trace, enabled: false };
455
474
 
456
475
  return {
457
476
  ...trace,
@@ -225,7 +225,7 @@ describe('chatSelectors', () => {
225
225
 
226
226
  const chats = chatSelectors.currentChatsWithGuideMessage(metaData)(state);
227
227
 
228
- expect(chats[0].content).toEqual('inbox.defaultMessage'); // Assuming translation returns a string containing this
228
+ expect(chats[0].content).toEqual(''); // Assuming translation returns a string containing this
229
229
  });
230
230
 
231
231
  it('should use agent default message for non-inbox sessions', () => {
@@ -260,4 +260,30 @@ describe('chatSelectors', () => {
260
260
  vi.restoreAllMocks();
261
261
  });
262
262
  });
263
+
264
+ describe('showInboxWelcome', () => {
265
+ it('should return false if the active session is not the inbox session', () => {
266
+ const state = merge(initialStore, { activeId: 'someActiveId' });
267
+ const result = chatSelectors.showInboxWelcome(state);
268
+ expect(result).toBe(false);
269
+ });
270
+
271
+ it('should return false if there are existing messages in the inbox session', () => {
272
+ const state = merge(initialStore, {
273
+ activeId: INBOX_SESSION_ID,
274
+ messages: mockMessages,
275
+ });
276
+ const result = chatSelectors.showInboxWelcome(state);
277
+ expect(result).toBe(false);
278
+ });
279
+
280
+ it('should return true if the active session is the inbox session and there are no existing messages', () => {
281
+ const state = merge(initialStore, {
282
+ activeId: INBOX_SESSION_ID,
283
+ messages: [],
284
+ });
285
+ const result = chatSelectors.showInboxWelcome(state);
286
+ expect(result).toBe(true);
287
+ });
288
+ });
263
289
  });
@@ -52,6 +52,15 @@ const currentChats = (s: ChatStore): ChatMessage[] => {
52
52
  };
53
53
 
54
54
  const initTime = Date.now();
55
+
56
+ const showInboxWelcome = (s: ChatStore): boolean => {
57
+ const isInbox = s.activeId === INBOX_SESSION_ID;
58
+ if (!isInbox) return false;
59
+ const data = currentChats(s);
60
+ const isBrandNewChat = data.length === 0;
61
+ return isBrandNewChat;
62
+ };
63
+
55
64
  // 针对新助手添加初始化时的自定义消息
56
65
  const currentChatsWithGuideMessage =
57
66
  (meta: MetaData) =>
@@ -64,7 +73,7 @@ const currentChatsWithGuideMessage =
64
73
 
65
74
  const [activeId, isInbox] = [s.activeId, s.activeId === INBOX_SESSION_ID];
66
75
 
67
- const inboxMsg = t('inbox.defaultMessage', { ns: 'chat' });
76
+ const inboxMsg = '';
68
77
  const agentSystemRoleMsg = t('agentDefaultMessageWithSystemRole', {
69
78
  name: meta.title || t('defaultAgent'),
70
79
  ns: 'chat',
@@ -137,4 +146,5 @@ export const chatSelectors = {
137
146
  getMessageById,
138
147
  getTraceIdByMessageId,
139
148
  latestMessage,
149
+ showInboxWelcome,
140
150
  };