@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.
- package/CHANGELOG.md +50 -0
- package/locales/ar/chat.json +2 -3
- package/locales/ar/welcome.json +34 -0
- package/locales/bg-BG/chat.json +2 -3
- package/locales/bg-BG/welcome.json +34 -0
- package/locales/de-DE/chat.json +0 -1
- package/locales/de-DE/welcome.json +34 -0
- package/locales/en-US/chat.json +0 -1
- package/locales/en-US/welcome.json +34 -0
- package/locales/es-ES/chat.json +0 -1
- package/locales/es-ES/welcome.json +34 -0
- package/locales/fr-FR/chat.json +0 -1
- package/locales/fr-FR/welcome.json +34 -0
- package/locales/it-IT/chat.json +0 -1
- package/locales/it-IT/welcome.json +34 -0
- package/locales/ja-JP/chat.json +0 -1
- package/locales/ja-JP/welcome.json +35 -1
- package/locales/ko-KR/chat.json +0 -1
- package/locales/ko-KR/welcome.json +34 -0
- package/locales/nl-NL/chat.json +3 -4
- package/locales/nl-NL/welcome.json +34 -0
- package/locales/pl-PL/chat.json +2 -3
- package/locales/pl-PL/welcome.json +34 -0
- package/locales/pt-BR/chat.json +0 -1
- package/locales/pt-BR/welcome.json +34 -0
- package/locales/ru-RU/chat.json +0 -1
- package/locales/ru-RU/welcome.json +34 -0
- package/locales/tr-TR/chat.json +1 -2
- package/locales/tr-TR/welcome.json +34 -0
- package/locales/vi-VN/chat.json +0 -1
- package/locales/vi-VN/welcome.json +34 -0
- package/locales/zh-CN/chat.json +0 -1
- package/locales/zh-CN/welcome.json +34 -0
- package/locales/zh-TW/chat.json +0 -1
- package/locales/zh-TW/welcome.json +34 -0
- package/next.config.mjs +1 -0
- package/package.json +2 -2
- package/src/app/chat/(desktop)/features/ChatInput/Footer/SendMore.tsx +2 -2
- package/src/app/chat/features/Migration/Failed.tsx +2 -1
- package/src/config/client.ts +0 -5
- package/src/const/guide.ts +86 -0
- package/src/const/url.ts +15 -8
- package/src/features/ChatInput/useSend.ts +8 -2
- package/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx +108 -0
- package/src/features/Conversation/components/InboxWelcome/QuestionSuggest.tsx +99 -0
- package/src/features/Conversation/components/InboxWelcome/index.tsx +76 -0
- package/src/features/Conversation/components/VirtualizedList/index.tsx +18 -5
- package/src/features/Conversation/index.tsx +6 -9
- package/src/layout/GlobalProvider/AppTheme.tsx +2 -1
- package/src/locales/default/chat.ts +0 -2
- package/src/locales/default/welcome.ts +35 -0
- package/src/services/chat.ts +50 -25
- package/src/store/chat/slices/message/action.test.ts +1 -5
- package/src/store/chat/slices/message/action.ts +23 -10
- package/src/store/chat/slices/message/selectors.test.ts +27 -1
- package/src/store/chat/slices/message/selectors.ts +11 -1
- package/src/store/chat/slices/plugin/action.test.ts +1 -1
- 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
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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>
|
|
@@ -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: '跳过创建',
|
package/src/services/chat.ts
CHANGED
|
@@ -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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
|
462
|
+
systemMessage.content = [systemMessage.content, injectSystemRoles]
|
|
463
|
+
.filter(Boolean)
|
|
464
|
+
.join('\n\n');
|
|
440
465
|
} else {
|
|
441
466
|
draft.unshift({
|
|
442
|
-
content:
|
|
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 () => {
|