@lobehub/chat 0.151.0 → 0.151.2

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 (58) hide show
  1. package/CHANGELOG.md +50 -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/next.config.mjs +1 -0
  37. package/package.json +2 -2
  38. package/src/app/chat/(desktop)/features/ChatInput/Footer/SendMore.tsx +2 -2
  39. package/src/app/chat/features/Migration/Failed.tsx +2 -1
  40. package/src/config/client.ts +0 -5
  41. package/src/const/guide.ts +86 -0
  42. package/src/const/url.ts +15 -8
  43. package/src/features/ChatInput/useSend.ts +8 -2
  44. package/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx +108 -0
  45. package/src/features/Conversation/components/InboxWelcome/QuestionSuggest.tsx +99 -0
  46. package/src/features/Conversation/components/InboxWelcome/index.tsx +76 -0
  47. package/src/features/Conversation/components/VirtualizedList/index.tsx +18 -5
  48. package/src/features/Conversation/index.tsx +6 -9
  49. package/src/layout/GlobalProvider/AppTheme.tsx +2 -1
  50. package/src/locales/default/chat.ts +0 -2
  51. package/src/locales/default/welcome.ts +35 -0
  52. package/src/services/chat.ts +50 -25
  53. package/src/store/chat/slices/message/action.test.ts +1 -5
  54. package/src/store/chat/slices/message/action.ts +23 -10
  55. package/src/store/chat/slices/message/selectors.test.ts +27 -1
  56. package/src/store/chat/slices/message/selectors.ts +11 -1
  57. package/src/store/chat/slices/plugin/action.test.ts +1 -1
  58. package/src/store/chat/slices/plugin/action.ts +1 -1
@@ -0,0 +1,108 @@
1
+ 'use client';
2
+
3
+ import { ActionIcon, Avatar, Grid } from '@lobehub/ui';
4
+ import { Skeleton, Typography } from 'antd';
5
+ import { createStyles } from 'antd-style';
6
+ import isEqual from 'fast-deep-equal';
7
+ import { RefreshCw } from 'lucide-react';
8
+ import Link from 'next/link';
9
+ import { memo, useMemo, useState } from 'react';
10
+ import { useTranslation } from 'react-i18next';
11
+ import { Flexbox } from 'react-layout-kit';
12
+
13
+ import { agentMarketSelectors, useMarketStore } from '@/store/market';
14
+
15
+ const { Paragraph } = Typography;
16
+
17
+ const useStyles = createStyles(({ css, token }) => ({
18
+ card: css`
19
+ position: relative;
20
+
21
+ height: 100%;
22
+ min-height: 110px;
23
+ padding: 16px;
24
+
25
+ color: ${token.colorText};
26
+
27
+ background: ${token.colorBgContainer};
28
+ border-radius: ${token.borderRadius}px;
29
+
30
+ &:hover {
31
+ background: ${token.colorBgElevated};
32
+ }
33
+ `,
34
+ cardDesc: css`
35
+ margin-block: 0 !important;
36
+ color: ${token.colorTextDescription};
37
+ `,
38
+ cardTitle: css`
39
+ margin-block: 0 !important;
40
+ font-size: 16px;
41
+ font-weight: bold;
42
+ `,
43
+ icon: css`
44
+ color: ${token.colorTextSecondary};
45
+ `,
46
+ title: css`
47
+ color: ${token.colorTextDescription};
48
+ `,
49
+ }));
50
+
51
+ const AgentsSuggest = memo(() => {
52
+ const { t } = useTranslation('welcome');
53
+ const [sliceStart, setSliceStart] = useState(0);
54
+ const useFetchAgentList = useMarketStore((s) => s.useFetchAgentList);
55
+ const { isLoading } = useFetchAgentList();
56
+ const agentList = useMarketStore((s) => agentMarketSelectors.getAgentList(s), isEqual);
57
+ const { styles } = useStyles();
58
+
59
+ const loadingCards = Array.from({ length: 4 }).map((_, index) => (
60
+ <Flexbox className={styles.card} key={index}>
61
+ <Skeleton active avatar paragraph={{ rows: 2 }} title={false} />
62
+ </Flexbox>
63
+ ));
64
+
65
+ const cards = useMemo(
66
+ () =>
67
+ agentList.slice(sliceStart, sliceStart + 4).map((agent) => (
68
+ <Link href={`/market?agent=${agent.identifier}`} key={agent.identifier}>
69
+ <Flexbox className={styles.card} gap={8} horizontal>
70
+ <Avatar avatar={agent.meta.avatar} style={{ flex: 'none' }} />
71
+ <Flexbox gap={8}>
72
+ <Paragraph className={styles.cardTitle} ellipsis={{ rows: 1 }}>
73
+ {agent.meta.title}
74
+ </Paragraph>
75
+ <Paragraph className={styles.cardDesc} ellipsis={{ rows: 2 }}>
76
+ {agent.meta.description}
77
+ </Paragraph>
78
+ </Flexbox>
79
+ </Flexbox>
80
+ </Link>
81
+ )),
82
+ [agentList, sliceStart],
83
+ );
84
+
85
+ const handleRefresh = () => {
86
+ if (!agentList) return;
87
+ setSliceStart(Math.floor((Math.random() * agentList.length) / 2));
88
+ };
89
+
90
+ return (
91
+ <Flexbox gap={8} width={'100%'}>
92
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
93
+ <div className={styles.title}>{t('guide.agents.title')}</div>
94
+ <ActionIcon
95
+ icon={RefreshCw}
96
+ onClick={handleRefresh}
97
+ size={{ blockSize: 24, fontSize: 14 }}
98
+ title={t('guide.agents.replaceBtn')}
99
+ />
100
+ </Flexbox>
101
+ <Grid gap={8} rows={2}>
102
+ {isLoading ? loadingCards : cards}
103
+ </Grid>
104
+ </Flexbox>
105
+ );
106
+ });
107
+
108
+ export default AgentsSuggest;
@@ -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 { useSendMessage } from '@/features/ChatInput/useSend';
14
+ import { useChatStore } from '@/store/chat';
15
+
16
+ const useStyles = createStyles(({ css, token }) => ({
17
+ card: css`
18
+ cursor: pointer;
19
+
20
+ padding: 12px 24px;
21
+
22
+ color: ${token.colorText};
23
+
24
+ background: ${token.colorBgContainer};
25
+ border-radius: 48px;
26
+
27
+ &:hover {
28
+ background: ${token.colorBgElevated};
29
+ }
30
+ `,
31
+ icon: css`
32
+ color: ${token.colorTextSecondary};
33
+ `,
34
+ title: css`
35
+ color: ${token.colorTextDescription};
36
+ `,
37
+ }));
38
+
39
+ const qa = shuffle([
40
+ 'q01',
41
+ 'q02',
42
+ 'q03',
43
+ 'q04',
44
+ 'q05',
45
+ 'q06',
46
+ 'q07',
47
+ 'q08',
48
+ 'q09',
49
+ 'q10',
50
+ 'q11',
51
+ 'q12',
52
+ 'q13',
53
+ 'q14',
54
+ 'q15',
55
+ ]).slice(0, 5);
56
+
57
+ const QuestionSuggest = memo(() => {
58
+ const [updateInputMessage] = useChatStore((s) => [s.updateInputMessage]);
59
+ const { t } = useTranslation('welcome');
60
+ const { styles } = useStyles();
61
+ const sendMessage = useSendMessage();
62
+
63
+ return (
64
+ <Flexbox gap={8} width={'100%'}>
65
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
66
+ <div className={styles.title}>{t('guide.questions.title')}</div>
67
+ <Link href={USAGE_DOCUMENTS} target={'_blank'}>
68
+ <ActionIcon
69
+ icon={ArrowRight}
70
+ size={{ blockSize: 24, fontSize: 16 }}
71
+ title={t('guide.questions.moreBtn')}
72
+ />
73
+ </Link>
74
+ </Flexbox>
75
+ <Flexbox gap={8} horizontal wrap={'wrap'}>
76
+ {qa.map((item) => {
77
+ const text = t(`guide.qa.${item}` as any);
78
+ return (
79
+ <Flexbox
80
+ align={'center'}
81
+ className={styles.card}
82
+ gap={8}
83
+ horizontal
84
+ key={item}
85
+ onClick={() => {
86
+ updateInputMessage(text);
87
+ sendMessage({ isWelcomeQuestion: true });
88
+ }}
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;
@@ -7,12 +7,19 @@ import { useChatStore } from '@/store/chat';
7
7
  import { chatSelectors } from '@/store/chat/selectors';
8
8
  import { isMobileScreen } from '@/utils/screen';
9
9
 
10
+ import { useInitConversation } from '../../hooks/useInitConversation';
10
11
  import AutoScroll from '../AutoScroll';
11
12
  import Item from '../ChatItem';
13
+ import InboxWelcome from '../InboxWelcome';
14
+ import SkeletonList from '../SkeletonList';
15
+
16
+ const WELCOME_ID = 'welcome';
12
17
 
13
18
  const itemContent = (index: number, id: string) => {
14
19
  const isMobile = isMobileScreen();
15
20
 
21
+ if (id === WELCOME_ID) return <InboxWelcome />;
22
+
16
23
  return index === 0 ? (
17
24
  <div style={{ height: 24 + (isMobile ? 0 : 64) }} />
18
25
  ) : (
@@ -24,18 +31,22 @@ interface VirtualizedListProps {
24
31
  mobile?: boolean;
25
32
  }
26
33
  const VirtualizedList = memo<VirtualizedListProps>(({ mobile }) => {
34
+ useInitConversation();
35
+
27
36
  const virtuosoRef = useRef<VirtuosoHandle>(null);
28
37
  const [atBottom, setAtBottom] = useState(true);
29
38
 
30
- const data = useChatStore(
31
- (s) => ['empty', ...chatSelectors.currentChatIDsWithGuideMessage(s)],
32
- isEqual,
33
- );
34
39
  const [id, chatLoading] = useChatStore((s) => [
35
40
  chatSelectors.currentChatKey(s),
36
41
  chatSelectors.currentChatLoadingState(s),
37
42
  ]);
38
43
 
44
+ const data = useChatStore((s) => {
45
+ const showInboxWelcome = chatSelectors.showInboxWelcome(s);
46
+ const ids = showInboxWelcome ? [WELCOME_ID] : chatSelectors.currentChatIDsWithGuideMessage(s);
47
+ return ['empty', ...ids];
48
+ }, isEqual);
49
+
39
50
  useEffect(() => {
40
51
  if (virtuosoRef.current) {
41
52
  virtuosoRef.current.scrollToIndex({ align: 'end', behavior: 'auto', index: 'LAST' });
@@ -45,7 +56,9 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile }) => {
45
56
  // overscan should be 1.5 times the height of the window
46
57
  const overscan = typeof window !== 'undefined' ? window.innerHeight * 1.5 : 0;
47
58
 
48
- return chatLoading && data.length === 2 ? null : (
59
+ return chatLoading ? (
60
+ <SkeletonList mobile={mobile} />
61
+ ) : (
49
62
  <Flexbox height={'100%'}>
50
63
  <Virtuoso
51
64
  atBottomStateChange={setAtBottom}
@@ -1,13 +1,12 @@
1
1
  import { createStyles } from 'antd-style';
2
- import { ReactNode, memo } from 'react';
2
+ import { ReactNode, Suspense, lazy, memo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
5
  import ChatHydration from '@/components/StoreHydration/ChatHydration';
6
- import { useChatStore } from '@/store/chat';
7
6
 
8
7
  import SkeletonList from './components/SkeletonList';
9
- import ChatList from './components/VirtualizedList';
10
- import { useInitConversation } from './hooks/useInitConversation';
8
+
9
+ const ChatList = lazy(() => import('./components/VirtualizedList'));
11
10
 
12
11
  const useStyles = createStyles(
13
12
  ({ css, responsive, stylish }) => css`
@@ -30,10 +29,6 @@ interface ConversationProps {
30
29
  const Conversation = memo<ConversationProps>(({ chatInput, mobile }) => {
31
30
  const { styles } = useStyles();
32
31
 
33
- useInitConversation();
34
-
35
- const [messagesInit] = useChatStore((s) => [s.messagesInit]);
36
-
37
32
  return (
38
33
  <Flexbox
39
34
  flex={1}
@@ -41,7 +36,9 @@ const Conversation = memo<ConversationProps>(({ chatInput, mobile }) => {
41
36
  style={{ position: 'relative' }}
42
37
  >
43
38
  <div className={styles}>
44
- {messagesInit ? <ChatList mobile={mobile} /> : <SkeletonList mobile={mobile} />}
39
+ <Suspense fallback={<SkeletonList mobile={mobile} />}>
40
+ <ChatList mobile={mobile} />
41
+ </Suspense>
45
42
  </div>
46
43
  {chatInput}
47
44
  <ChatHydration />
@@ -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';
@@ -29,6 +31,7 @@ import { createHeaderWithAuth, getProviderAuthPayload } from './_auth';
29
31
  import { API_ENDPOINTS } from './_url';
30
32
 
31
33
  interface FetchOptions {
34
+ isWelcomeQuestion?: boolean;
32
35
  signal?: AbortSignal | undefined;
33
36
  trace?: TracePayload;
34
37
  }
@@ -63,6 +66,7 @@ interface FetchAITaskResultParams {
63
66
 
64
67
  interface CreateAssistantMessageStream extends FetchSSEOptions {
65
68
  abortController?: AbortController;
69
+ isWelcomeQuestion?: boolean;
66
70
  params: GetChatCompletionPayload;
67
71
  trace?: TracePayload;
68
72
  }
@@ -183,11 +187,14 @@ class ChatService {
183
187
  );
184
188
  // ============ 1. preprocess messages ============ //
185
189
 
186
- const oaiMessages = this.processMessages({
187
- messages,
188
- model: payload.model,
189
- tools: enabledPlugins,
190
- });
190
+ const oaiMessages = this.processMessages(
191
+ {
192
+ messages,
193
+ model: payload.model,
194
+ tools: enabledPlugins,
195
+ },
196
+ options,
197
+ );
191
198
 
192
199
  // ============ 2. preprocess tools ============ //
193
200
 
@@ -215,10 +222,12 @@ class ChatService {
215
222
  onErrorHandle,
216
223
  onFinish,
217
224
  trace,
225
+ isWelcomeQuestion,
218
226
  }: CreateAssistantMessageStream) => {
219
227
  await fetchSSE(
220
228
  () =>
221
229
  this.createAssistantMessage(params, {
230
+ isWelcomeQuestion,
222
231
  signal: abortController?.signal,
223
232
  trace: this.mapTrace(trace, TraceTagMap.Chat),
224
233
  }),
@@ -371,15 +380,18 @@ class ChatService {
371
380
  return await data?.text();
372
381
  };
373
382
 
374
- private processMessages = ({
375
- messages,
376
- tools,
377
- model,
378
- }: {
379
- messages: ChatMessage[];
380
- model: string;
381
- tools?: string[];
382
- }): OpenAIChatMessage[] => {
383
+ private processMessages = (
384
+ {
385
+ messages,
386
+ tools,
387
+ model,
388
+ }: {
389
+ messages: ChatMessage[];
390
+ model: string;
391
+ tools?: string[];
392
+ },
393
+ options?: FetchOptions,
394
+ ): OpenAIChatMessage[] => {
383
395
  // handle content type for vision model
384
396
  // for the models with visual ability, add image url to content
385
397
  // refs: https://platform.openai.com/docs/guides/vision/quick-start
@@ -424,22 +436,35 @@ class ChatService {
424
436
  });
425
437
 
426
438
  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;
439
+ // if it's a welcome question, inject InboxGuide SystemRole
440
+ const inboxGuideSystemRole =
441
+ options?.isWelcomeQuestion &&
442
+ options?.trace?.sessionId === INBOX_SESSION_ID &&
443
+ INBOX_GUIDE_SYSTEMROLE;
444
+
445
+ // Inject Tool SystemRole
446
+ const hasTools = tools && tools?.length > 0;
447
+ const hasFC =
448
+ hasTools &&
449
+ modelProviderSelectors.isModelEnabledFunctionCall(model)(useUserStore.getState());
450
+ const toolsSystemRoles =
451
+ hasFC && toolSelectors.enabledSystemRoles(tools)(useToolStore.getState());
452
+
453
+ const injectSystemRoles = [inboxGuideSystemRole, toolsSystemRoles]
454
+ .filter(Boolean)
455
+ .join('\n\n');
456
+
457
+ if (!injectSystemRoles) return;
432
458
 
433
459
  const systemMessage = draft.find((i) => i.role === 'system');
434
460
 
435
- const toolsSystemRoles = toolSelectors.enabledSystemRoles(tools)(useToolStore.getState());
436
- if (!toolsSystemRoles) return;
437
-
438
461
  if (systemMessage) {
439
- systemMessage.content = systemMessage.content + '\n\n' + toolsSystemRoles;
462
+ systemMessage.content = [systemMessage.content, injectSystemRoles]
463
+ .filter(Boolean)
464
+ .join('\n\n');
440
465
  } else {
441
466
  draft.unshift({
442
- content: toolsSystemRoles,
467
+ content: injectSystemRoles,
443
468
  role: 'system',
444
469
  });
445
470
  }
@@ -451,7 +476,7 @@ class ChatService {
451
476
 
452
477
  const enabled = preferenceSelectors.userAllowTrace(useUserStore.getState());
453
478
 
454
- if (!enabled) return { enabled: false };
479
+ if (!enabled) return { ...trace, enabled: false };
455
480
 
456
481
  return {
457
482
  ...trace,
@@ -385,11 +385,7 @@ describe('chatMessage actions', () => {
385
385
  });
386
386
 
387
387
  expect(messageService.removeMessage).not.toHaveBeenCalledWith(messageId);
388
- expect(mockState.coreProcessMessage).toHaveBeenCalledWith(
389
- expect.any(Array),
390
- messageId,
391
- undefined,
392
- );
388
+ expect(mockState.coreProcessMessage).toHaveBeenCalledWith(expect.any(Array), messageId, {});
393
389
  });
394
390
 
395
391
  it('should not perform any action if the message id does not exist', async () => {