@lobehub/chat 0.100.5 → 0.101.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.
- package/.eslintrc.js +1 -0
- package/CHANGELOG.md +50 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/__mocks__/zustand/traditional.ts +25 -0
- package/package.json +7 -3
- package/src/app/api/files/image/imgur.ts +72 -0
- package/src/app/api/files/image/route.ts +42 -0
- package/src/app/api/openai/chat/createChatCompletion.ts +2 -1
- package/src/app/chat/(desktop)/features/ChatInput/DragUpload.tsx +166 -0
- package/src/app/chat/(desktop)/features/ChatInput/Footer/LocalFiles.tsx +10 -0
- package/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +64 -0
- package/src/app/chat/(desktop)/features/ChatInput/InputArea.tsx +3 -3
- package/src/app/chat/(desktop)/features/ChatInput/index.tsx +38 -30
- package/src/app/chat/components/FileList/FileItem.style.ts +81 -0
- package/src/app/chat/components/FileList/FileItem.tsx +69 -0
- package/src/app/chat/components/FileList/Lightbox.tsx +46 -0
- package/src/app/chat/components/FileList/index.tsx +63 -0
- package/src/app/chat/features/ChatInput/ActionBar/Clear.tsx +13 -4
- package/src/app/chat/features/ChatInput/ActionBar/FileUpload.tsx +53 -0
- package/src/app/chat/features/ChatInput/ActionBar/config.ts +3 -1
- package/src/app/chat/{(desktop)/features → features}/ChatInput/useSend.ts +5 -1
- package/src/app/chat/features/Conversation/ChatList/Messages/User.tsx +23 -0
- package/src/app/chat/features/Conversation/ChatList/Messages/index.ts +2 -0
- package/src/app/market/(desktop)/features/AgentDetail.tsx +14 -9
- package/src/app/market/(desktop)/index.tsx +2 -3
- package/src/app/market/(mobile)/index.tsx +2 -3
- package/src/app/market/features/AgentCard/AgentCardItem.tsx +4 -1
- package/src/app/market/features/AgentCard/index.tsx +20 -31
- package/src/app/market/features/AgentDetailContent/AgentInfo/Header.tsx +0 -1
- package/src/app/market/features/AgentDetailContent/AgentInfo/index.tsx +2 -3
- package/src/app/market/features/AgentSearchBar/index.tsx +9 -4
- package/src/app/market/features/TagList/Inner.tsx +28 -0
- package/src/app/market/features/TagList/index.tsx +12 -23
- package/src/config/__tests__/client.test.ts +76 -0
- package/src/config/__tests__/server.test.ts +52 -0
- package/src/config/server.ts +8 -0
- package/src/const/layoutTokens.ts +1 -1
- package/src/const/llm.ts +3 -0
- package/src/const/settings.ts +2 -0
- package/src/database/core/__tests__/db.test.ts +77 -0
- package/src/database/core/__tests__/model.test.ts +55 -0
- package/src/database/core/db.ts +25 -0
- package/src/database/core/index.ts +1 -0
- package/src/database/core/model.ts +49 -0
- package/src/database/core/schema.ts +9 -0
- package/src/database/models/file.test.ts +71 -0
- package/src/database/models/file.ts +26 -0
- package/src/locales/default/chat.ts +5 -0
- package/src/services/__tests__/file.test.ts +88 -0
- package/src/services/chatModel.ts +11 -6
- package/src/services/file.ts +36 -0
- package/src/store/files/index.ts +2 -0
- package/src/store/files/initialState.ts +7 -0
- package/src/store/files/selectors.test.ts +75 -0
- package/src/store/files/selectors.ts +34 -0
- package/src/store/files/slices/images/action.test.ts +186 -0
- package/src/store/files/slices/images/action.ts +83 -0
- package/src/store/files/slices/images/index.ts +3 -0
- package/src/store/files/slices/images/initialState.ts +11 -0
- package/src/store/files/store.ts +27 -0
- package/src/store/global/selectors/settings.test.ts +1 -3
- package/src/store/market/action.ts +9 -1
- package/src/store/market/initialState.ts +2 -0
- package/src/store/market/selectors.ts +10 -8
- package/src/store/session/slices/agentConfig/selectors.ts +7 -1
- package/src/store/session/slices/chat/actions/message.ts +78 -17
- package/src/store/session/slices/chat/reducers/files.test.ts +38 -0
- package/src/store/session/slices/chat/reducers/files.ts +37 -0
- package/src/store/session/slices/session/initialState.ts +1 -0
- package/src/store/session/slices/session/reducers/session.test.ts +98 -1
- package/src/store/session/slices/session/reducers/session.ts +26 -1
- package/src/types/chatMessage.ts +3 -2
- package/src/types/database/db.ts +4 -0
- package/src/types/database/files.ts +38 -0
- package/src/types/files.ts +8 -0
- package/src/types/market.ts +1 -0
- package/src/types/openai/chat.ts +15 -1
- package/src/types/session.ts +1 -0
- package/tests/setup.ts +8 -0
- package/vitest.config.ts +2 -0
- package/src/app/chat/(desktop)/features/ChatInput/Footer.tsx +0 -63
- package/src/utils/findDuplicates.ts +0 -22
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createStyles } from 'antd-style';
|
|
2
|
+
|
|
3
|
+
export const IMAGE_SIZE = 64;
|
|
4
|
+
const imageBorderRaidus = 8;
|
|
5
|
+
|
|
6
|
+
export const useStyles = createStyles(({ css, cx, token, isDarkMode }) => {
|
|
7
|
+
const closeIcon = cx(css`
|
|
8
|
+
cursor: pointer;
|
|
9
|
+
|
|
10
|
+
position: absolute;
|
|
11
|
+
top: -6px;
|
|
12
|
+
right: -6px;
|
|
13
|
+
|
|
14
|
+
width: 16px;
|
|
15
|
+
height: 16px;
|
|
16
|
+
|
|
17
|
+
color: ${isDarkMode ? token.colorTextQuaternary : token.colorTextTertiary};
|
|
18
|
+
|
|
19
|
+
opacity: 0;
|
|
20
|
+
background: ${isDarkMode ? token.colorTextSecondary : token.colorBgContainer};
|
|
21
|
+
border-radius: 50%;
|
|
22
|
+
|
|
23
|
+
transition: all 0.2s ease-in;
|
|
24
|
+
|
|
25
|
+
&:hover {
|
|
26
|
+
scale: 1.2;
|
|
27
|
+
}
|
|
28
|
+
`);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
closeIcon,
|
|
32
|
+
container: css`
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
|
|
35
|
+
position: relative;
|
|
36
|
+
|
|
37
|
+
width: ${IMAGE_SIZE}px;
|
|
38
|
+
height: ${IMAGE_SIZE}px;
|
|
39
|
+
|
|
40
|
+
border-radius: ${imageBorderRaidus}px;
|
|
41
|
+
|
|
42
|
+
&:hover {
|
|
43
|
+
.${closeIcon} {
|
|
44
|
+
opacity: 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`,
|
|
48
|
+
image: css`
|
|
49
|
+
opacity: ${isDarkMode ? 0.6 : 1};
|
|
50
|
+
object-fit: cover;
|
|
51
|
+
border-radius: 8px;
|
|
52
|
+
animation: fade-in 0.3s ease-in;
|
|
53
|
+
|
|
54
|
+
@keyframes fade-in {
|
|
55
|
+
from {
|
|
56
|
+
scale: 1.2;
|
|
57
|
+
opacity: 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
to {
|
|
61
|
+
scale: 1;
|
|
62
|
+
opacity: 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`,
|
|
66
|
+
imageCtn: css`
|
|
67
|
+
position: relative;
|
|
68
|
+
`,
|
|
69
|
+
imageWrapper: css`
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
width: ${IMAGE_SIZE}px;
|
|
72
|
+
height: ${IMAGE_SIZE}px;
|
|
73
|
+
border-radius: ${imageBorderRaidus}px;
|
|
74
|
+
`,
|
|
75
|
+
notFound: css`
|
|
76
|
+
color: ${token.colorTextSecondary};
|
|
77
|
+
background: ${token.colorFillTertiary};
|
|
78
|
+
border-radius: ${imageBorderRaidus}px;
|
|
79
|
+
`,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { CloseCircleFilled } from '@ant-design/icons';
|
|
2
|
+
import { Icon } from '@lobehub/ui';
|
|
3
|
+
import { Skeleton } from 'antd';
|
|
4
|
+
import { LucideImageOff } from 'lucide-react';
|
|
5
|
+
import Image from 'next/image';
|
|
6
|
+
import { memo } from 'react';
|
|
7
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
|
8
|
+
|
|
9
|
+
import { useFileStore } from '@/store/files';
|
|
10
|
+
|
|
11
|
+
import { IMAGE_SIZE, useStyles } from './FileItem.style';
|
|
12
|
+
|
|
13
|
+
const FileItem = memo<{ editable: boolean; id: string; onClick: () => void }>(
|
|
14
|
+
({ editable, id, onClick }) => {
|
|
15
|
+
const { styles } = useStyles();
|
|
16
|
+
const [useFetchFile, removeFile] = useFileStore((s) => [s.useFetchFile, s.removeFile]);
|
|
17
|
+
|
|
18
|
+
const { data, isLoading } = useFetchFile(id);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Flexbox className={styles.container} onClick={onClick}>
|
|
22
|
+
{isLoading ? (
|
|
23
|
+
<Skeleton
|
|
24
|
+
active
|
|
25
|
+
title={{
|
|
26
|
+
style: { borderRadius: 8, height: IMAGE_SIZE },
|
|
27
|
+
width: IMAGE_SIZE,
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
) : (
|
|
31
|
+
<Flexbox className={styles.imageCtn}>
|
|
32
|
+
<div className={styles.imageWrapper}>
|
|
33
|
+
{data ? (
|
|
34
|
+
<Image
|
|
35
|
+
alt={data.name || ''}
|
|
36
|
+
className={styles.image}
|
|
37
|
+
fetchPriority={'high'}
|
|
38
|
+
height={IMAGE_SIZE}
|
|
39
|
+
loading={'lazy'}
|
|
40
|
+
src={data.url}
|
|
41
|
+
width={IMAGE_SIZE}
|
|
42
|
+
/>
|
|
43
|
+
) : (
|
|
44
|
+
<Center className={styles.notFound} height={'100%'}>
|
|
45
|
+
<Icon icon={LucideImageOff} size={{ fontSize: 28 }} />
|
|
46
|
+
</Center>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
</Flexbox>
|
|
50
|
+
)}
|
|
51
|
+
{/* only show close icon when editable */}
|
|
52
|
+
{editable && (
|
|
53
|
+
<Center
|
|
54
|
+
className={styles.closeIcon}
|
|
55
|
+
onClick={(e) => {
|
|
56
|
+
e.stopPropagation();
|
|
57
|
+
|
|
58
|
+
removeFile(id);
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
<CloseCircleFilled />
|
|
62
|
+
</Center>
|
|
63
|
+
)}
|
|
64
|
+
</Flexbox>
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export default FileItem;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createStyles } from 'antd-style';
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import Lightbox from 'react-spring-lightbox';
|
|
4
|
+
|
|
5
|
+
import { filesSelectors, useFileStore } from '@/store/files';
|
|
6
|
+
|
|
7
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
8
|
+
wrapper: css`
|
|
9
|
+
background: ${token.colorBgMask};
|
|
10
|
+
backdrop-filter: blur(4px);
|
|
11
|
+
`,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
interface LightBoxProps {
|
|
15
|
+
index: number;
|
|
16
|
+
items: string[];
|
|
17
|
+
onNext: () => void;
|
|
18
|
+
onOpenChange: (open: boolean) => void;
|
|
19
|
+
onPrev: () => void;
|
|
20
|
+
open: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const LightBox = memo<LightBoxProps>(({ onOpenChange, open, items, index, onNext, onPrev }) => {
|
|
24
|
+
const { styles } = useStyles();
|
|
25
|
+
|
|
26
|
+
const list = useFileStore(filesSelectors.getImageDetailByList(items));
|
|
27
|
+
return (
|
|
28
|
+
<Lightbox
|
|
29
|
+
className={styles.wrapper}
|
|
30
|
+
currentIndex={index}
|
|
31
|
+
images={list.map((i) => ({
|
|
32
|
+
alt: i.name,
|
|
33
|
+
loading: 'lazy',
|
|
34
|
+
src: i.url,
|
|
35
|
+
}))}
|
|
36
|
+
isOpen={open}
|
|
37
|
+
onClose={() => {
|
|
38
|
+
onOpenChange(false);
|
|
39
|
+
}}
|
|
40
|
+
onNext={onNext}
|
|
41
|
+
onPrev={onPrev}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export default LightBox;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { memo, useState } from 'react';
|
|
2
|
+
import { Flexbox } from 'react-layout-kit';
|
|
3
|
+
|
|
4
|
+
import FileItem from './FileItem';
|
|
5
|
+
import Lightbox from './Lightbox';
|
|
6
|
+
|
|
7
|
+
// TODO:后续看情况是否做图片过多的滚动功能
|
|
8
|
+
// const useStyles = createStyles(({ css }) => ({
|
|
9
|
+
// container: css`
|
|
10
|
+
// width: ${IMAGE_SIZE * 5 + 4 * 8 + IMAGE_SIZE / 2}px;
|
|
11
|
+
// overflow-x: scroll;
|
|
12
|
+
// height: ${IMAGE_SIZE + 12}px;
|
|
13
|
+
// `,
|
|
14
|
+
// }));
|
|
15
|
+
|
|
16
|
+
interface FileListProps {
|
|
17
|
+
editable?: boolean;
|
|
18
|
+
items: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const FileList = memo<FileListProps>(({ items, editable = true }) => {
|
|
22
|
+
// const { styles } = useStyles();
|
|
23
|
+
|
|
24
|
+
const [showLightbox, setShowLightbox] = useState(false);
|
|
25
|
+
const [currentImageIndex, setCurrentIndex] = useState(0);
|
|
26
|
+
|
|
27
|
+
const gotoPrevious = () => currentImageIndex > 0 && setCurrentIndex(currentImageIndex - 1);
|
|
28
|
+
const gotoNext = () =>
|
|
29
|
+
currentImageIndex + 1 < items.length && setCurrentIndex(currentImageIndex + 1);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<Flexbox
|
|
34
|
+
align={'flex-end'}
|
|
35
|
+
// className={styles.container}
|
|
36
|
+
gap={8}
|
|
37
|
+
horizontal
|
|
38
|
+
>
|
|
39
|
+
{items.map((i, index) => (
|
|
40
|
+
<FileItem
|
|
41
|
+
editable={editable}
|
|
42
|
+
id={i}
|
|
43
|
+
key={i}
|
|
44
|
+
onClick={() => {
|
|
45
|
+
setCurrentIndex(index);
|
|
46
|
+
setShowLightbox(true);
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
))}
|
|
50
|
+
</Flexbox>
|
|
51
|
+
<Lightbox
|
|
52
|
+
index={currentImageIndex}
|
|
53
|
+
items={items}
|
|
54
|
+
onNext={gotoNext}
|
|
55
|
+
onOpenChange={setShowLightbox}
|
|
56
|
+
onPrev={gotoPrevious}
|
|
57
|
+
open={showLightbox}
|
|
58
|
+
/>
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export default FileList;
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import { ActionIcon } from '@lobehub/ui';
|
|
2
2
|
import { Popconfirm } from 'antd';
|
|
3
3
|
import { Eraser } from 'lucide-react';
|
|
4
|
-
import { memo } from 'react';
|
|
4
|
+
import { memo, useCallback } from 'react';
|
|
5
5
|
import { useHotkeys } from 'react-hotkeys-hook';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
8
8
|
import HotKeys from '@/components/HotKeys';
|
|
9
9
|
import { CLEAN_MESSAGE_KEY, PREFIX_KEY } from '@/const/hotkeys';
|
|
10
|
+
import { useFileStore } from '@/store/files';
|
|
10
11
|
import { useSessionStore } from '@/store/session';
|
|
11
12
|
|
|
12
13
|
const Clear = memo(() => {
|
|
13
14
|
const { t } = useTranslation('setting');
|
|
14
|
-
const [clearMessage] = useSessionStore((s) => [s.clearMessage
|
|
15
|
+
const [clearMessage] = useSessionStore((s) => [s.clearMessage]);
|
|
16
|
+
const [clearImageList] = useFileStore((s) => [s.clearImageList]);
|
|
15
17
|
const hotkeys = [PREFIX_KEY, CLEAN_MESSAGE_KEY].join('+');
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
const resetConversation = useCallback(() => {
|
|
20
|
+
clearMessage();
|
|
21
|
+
clearImageList();
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
useHotkeys(hotkeys, resetConversation, {
|
|
18
25
|
preventDefault: true,
|
|
19
26
|
});
|
|
20
27
|
|
|
@@ -23,7 +30,9 @@ const Clear = memo(() => {
|
|
|
23
30
|
cancelText={t('cancel', { ns: 'common' })}
|
|
24
31
|
okButtonProps={{ danger: true }}
|
|
25
32
|
okText={t('ok', { ns: 'common' })}
|
|
26
|
-
onConfirm={() =>
|
|
33
|
+
onConfirm={() => {
|
|
34
|
+
resetConversation();
|
|
35
|
+
}}
|
|
27
36
|
placement={'topRight'}
|
|
28
37
|
title={t('confirmClearCurrentMessages', { ns: 'chat' })}
|
|
29
38
|
>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ActionIcon, Icon } from '@lobehub/ui';
|
|
2
|
+
import { Upload } from 'antd';
|
|
3
|
+
import { useTheme } from 'antd-style';
|
|
4
|
+
import { LucideImage, LucideLoader2 } from 'lucide-react';
|
|
5
|
+
import { memo, useState } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Center } from 'react-layout-kit';
|
|
8
|
+
|
|
9
|
+
import { useFileStore } from '@/store/files';
|
|
10
|
+
import { useSessionStore } from '@/store/session';
|
|
11
|
+
import { agentSelectors } from '@/store/session/slices/agentConfig';
|
|
12
|
+
|
|
13
|
+
const FileUpload = memo(() => {
|
|
14
|
+
const { t } = useTranslation('chat');
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
const upload = useFileStore((s) => s.uploadFile);
|
|
18
|
+
|
|
19
|
+
const canUpload = useSessionStore(agentSelectors.modelHasVisionAbility);
|
|
20
|
+
|
|
21
|
+
if (!canUpload) return null;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Upload
|
|
25
|
+
accept="image/*"
|
|
26
|
+
beforeUpload={async (file) => {
|
|
27
|
+
setLoading(true);
|
|
28
|
+
|
|
29
|
+
await upload(file);
|
|
30
|
+
|
|
31
|
+
setLoading(false);
|
|
32
|
+
return false;
|
|
33
|
+
}}
|
|
34
|
+
multiple={true}
|
|
35
|
+
showUploadList={false}
|
|
36
|
+
>
|
|
37
|
+
{loading ? (
|
|
38
|
+
<Center height={36} width={36}>
|
|
39
|
+
<Icon
|
|
40
|
+
color={theme.colorTextSecondary}
|
|
41
|
+
icon={LucideLoader2}
|
|
42
|
+
size={{ fontSize: 18 }}
|
|
43
|
+
spin
|
|
44
|
+
></Icon>
|
|
45
|
+
</Center>
|
|
46
|
+
) : (
|
|
47
|
+
<ActionIcon icon={LucideImage} placement={'bottom'} title={t('upload.actionTooltip')} />
|
|
48
|
+
)}
|
|
49
|
+
</Upload>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export default FileUpload;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FC } from 'react';
|
|
2
2
|
|
|
3
3
|
import Clear from './Clear';
|
|
4
|
+
import FileUpload from './FileUpload';
|
|
4
5
|
import History from './History';
|
|
5
6
|
import ModelSwitch from './ModelSwitch';
|
|
6
7
|
import Temperature from './Temperature';
|
|
@@ -8,6 +9,7 @@ import Token from './Token';
|
|
|
8
9
|
|
|
9
10
|
export const actionMap: Record<string, FC> = {
|
|
10
11
|
clear: Clear,
|
|
12
|
+
fileUpload: FileUpload,
|
|
11
13
|
history: History,
|
|
12
14
|
model: ModelSwitch,
|
|
13
15
|
temperature: Temperature,
|
|
@@ -15,5 +17,5 @@ export const actionMap: Record<string, FC> = {
|
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
// we can make these action lists configurable in the future
|
|
18
|
-
export const leftActionList = ['model', 'temperature', 'history', 'token'];
|
|
20
|
+
export const leftActionList = ['model', 'fileUpload', 'temperature', 'history', 'token'];
|
|
19
21
|
export const rightActionList = ['clear'];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
|
|
3
|
+
import { filesSelectors, useFileStore } from '@/store/files';
|
|
3
4
|
import { useSessionStore } from '@/store/session';
|
|
4
5
|
|
|
5
6
|
export const useSendMessage = () => {
|
|
@@ -11,7 +12,10 @@ export const useSendMessage = () => {
|
|
|
11
12
|
return useCallback(() => {
|
|
12
13
|
const store = useSessionStore.getState();
|
|
13
14
|
if (!!store.chatLoadingId) return;
|
|
14
|
-
|
|
15
|
+
const imageList = filesSelectors.imageUrlOrBase64List(useFileStore.getState());
|
|
16
|
+
|
|
17
|
+
sendMessage(store.inputMessage, imageList);
|
|
15
18
|
updateInputMessage('');
|
|
19
|
+
useFileStore.getState().clearImageList();
|
|
16
20
|
}, []);
|
|
17
21
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ReactNode, memo } from 'react';
|
|
2
|
+
import { Flexbox } from 'react-layout-kit';
|
|
3
|
+
|
|
4
|
+
import FileList from '@/app/chat/components/FileList';
|
|
5
|
+
import { LOADING_FLAT } from '@/const/message';
|
|
6
|
+
import { ChatMessage } from '@/types/chatMessage';
|
|
7
|
+
|
|
8
|
+
import BubblesLoading from '../Loading';
|
|
9
|
+
|
|
10
|
+
export const UserMessage = memo<
|
|
11
|
+
ChatMessage & {
|
|
12
|
+
editableContent: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
>(({ id, editableContent, content, ...res }) => {
|
|
15
|
+
if (content === LOADING_FLAT) return <BubblesLoading />;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Flexbox gap={8} id={id}>
|
|
19
|
+
{editableContent}
|
|
20
|
+
{res.files && <FileList editable={false} items={res.files} />}
|
|
21
|
+
</Flexbox>
|
|
22
|
+
);
|
|
23
|
+
});
|
|
@@ -10,11 +10,13 @@ import { pathString } from '@/utils/url';
|
|
|
10
10
|
import { AssistantMessage } from './Assistant';
|
|
11
11
|
import { DefaultMessage } from './Default';
|
|
12
12
|
import { FunctionMessage } from './Function';
|
|
13
|
+
import { UserMessage } from './User';
|
|
13
14
|
|
|
14
15
|
export const renderMessages: ChatListProps['renderMessages'] = {
|
|
15
16
|
assistant: AssistantMessage,
|
|
16
17
|
default: DefaultMessage,
|
|
17
18
|
function: FunctionMessage,
|
|
19
|
+
user: UserMessage,
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
export const useAvatarsClick = (): ChatListProps['onAvatarsClick'] => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DraggablePanel, DraggablePanelBody, DraggablePanelContainer } from '@lobehub/ui';
|
|
2
2
|
import { createStyles } from 'antd-style';
|
|
3
|
-
import { memo, useState } from 'react';
|
|
3
|
+
import { memo, useCallback, useState } from 'react';
|
|
4
4
|
|
|
5
5
|
import SafeSpacing from '@/components/SafeSpacing';
|
|
6
6
|
import { MARKET_SIDEBAR_WIDTH } from '@/const/layoutTokens';
|
|
@@ -31,6 +31,18 @@ const SideBar = memo(() => {
|
|
|
31
31
|
s.activateAgent,
|
|
32
32
|
]);
|
|
33
33
|
|
|
34
|
+
const handleExpandChange = useCallback(
|
|
35
|
+
(show: boolean) => {
|
|
36
|
+
if (!show) {
|
|
37
|
+
setTempId(useMarketStore.getState().currentIdentifier);
|
|
38
|
+
deactivateAgent();
|
|
39
|
+
} else if (tempId) {
|
|
40
|
+
activateAgent(tempId);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
[deactivateAgent, activateAgent, tempId],
|
|
44
|
+
);
|
|
45
|
+
|
|
34
46
|
return (
|
|
35
47
|
<DraggablePanel
|
|
36
48
|
className={styles.drawer}
|
|
@@ -40,14 +52,7 @@ const SideBar = memo(() => {
|
|
|
40
52
|
expand={showAgentSidebar}
|
|
41
53
|
minWidth={MARKET_SIDEBAR_WIDTH}
|
|
42
54
|
mode={'fixed'}
|
|
43
|
-
onExpandChange={
|
|
44
|
-
if (!show) {
|
|
45
|
-
setTempId(useMarketStore.getState().currentIdentifier);
|
|
46
|
-
deactivateAgent();
|
|
47
|
-
} else if (tempId) {
|
|
48
|
-
activateAgent(tempId);
|
|
49
|
-
}
|
|
50
|
-
}}
|
|
55
|
+
onExpandChange={handleExpandChange}
|
|
51
56
|
placement={'right'}
|
|
52
57
|
>
|
|
53
58
|
<DraggablePanelContainer
|
|
@@ -6,18 +6,17 @@ import { FC, memo } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import AgentCard from '@/app/market/features/AgentCard';
|
|
8
8
|
import ResponsiveIndex from '@/components/ResponsiveIndex';
|
|
9
|
-
import { AgentsMarketIndexItem } from '@/types/market';
|
|
10
9
|
|
|
11
10
|
import Index from '../index';
|
|
12
11
|
import Layout from './layout.desktop';
|
|
13
12
|
|
|
14
13
|
const Mobile: FC = dynamic(() => import('../(mobile)'), { ssr: false }) as FC;
|
|
15
14
|
|
|
16
|
-
export default memo
|
|
15
|
+
export default memo(() => (
|
|
17
16
|
<ResponsiveIndex Mobile={Mobile}>
|
|
18
17
|
<Layout>
|
|
19
18
|
<Index />
|
|
20
|
-
<AgentCard CardRender={SpotlightCard}
|
|
19
|
+
<AgentCard CardRender={SpotlightCard} />
|
|
21
20
|
</Layout>
|
|
22
21
|
</ResponsiveIndex>
|
|
23
22
|
));
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
import { memo } from 'react';
|
|
4
4
|
|
|
5
5
|
import AgentCard from '@/app/market/features/AgentCard';
|
|
6
|
-
import { AgentsMarketIndexItem } from '@/types/market';
|
|
7
6
|
|
|
8
7
|
import Index from '../index';
|
|
9
8
|
import CardRender from './features/AgentCard';
|
|
10
9
|
import Layout from './layout.mobile';
|
|
11
10
|
|
|
12
|
-
export default memo
|
|
11
|
+
export default memo(() => (
|
|
13
12
|
<Layout>
|
|
14
13
|
<Index />
|
|
15
|
-
<AgentCard CardRender={CardRender}
|
|
14
|
+
<AgentCard CardRender={CardRender} mobile />
|
|
16
15
|
</Layout>
|
|
17
16
|
));
|
|
@@ -13,13 +13,16 @@ import AgentCardBanner from './AgentCardBanner';
|
|
|
13
13
|
import { useStyles } from './style';
|
|
14
14
|
|
|
15
15
|
const { Paragraph } = Typography;
|
|
16
|
+
|
|
16
17
|
const AgentCardItem = memo<AgentsMarketIndexItem>(({ meta, identifier }) => {
|
|
17
18
|
const ref = useRef(null);
|
|
18
19
|
const isHovering = useHover(ref);
|
|
19
20
|
const onAgentCardClick = useMarketStore((s) => s.activateAgent);
|
|
20
|
-
const { avatar, title, description, tags, backgroundColor } = meta;
|
|
21
21
|
const { styles, theme } = useStyles();
|
|
22
22
|
const { isDarkMode } = useThemeMode();
|
|
23
|
+
|
|
24
|
+
const { avatar, title, description, tags, backgroundColor } = meta;
|
|
25
|
+
|
|
23
26
|
return (
|
|
24
27
|
<Flexbox className={styles.container} onClick={() => onAgentCardClick(identifier)}>
|
|
25
28
|
<AgentCardBanner meta={meta} style={{ opacity: isDarkMode ? 0.9 : 0.4 }} />
|
|
@@ -1,71 +1,60 @@
|
|
|
1
1
|
import { SpotlightCardProps } from '@lobehub/ui';
|
|
2
|
-
import
|
|
2
|
+
import isEqual from 'fast-deep-equal';
|
|
3
|
+
import { FC, memo, useCallback } from 'react';
|
|
3
4
|
import { useTranslation } from 'react-i18next';
|
|
4
5
|
import { Flexbox } from 'react-layout-kit';
|
|
5
6
|
import LazyLoad from 'react-lazy-load';
|
|
6
7
|
|
|
7
8
|
import { agentMarketSelectors, useMarketStore } from '@/store/market';
|
|
8
|
-
import { AgentsMarketIndexItem } from '@/types/market';
|
|
9
9
|
|
|
10
10
|
import TagList from '../TagList';
|
|
11
11
|
import AgentCardItem from './AgentCardItem';
|
|
12
12
|
import Loading from './Loading';
|
|
13
13
|
import { useStyles } from './style';
|
|
14
14
|
|
|
15
|
-
const gridRender: SpotlightCardProps['renderItem'] = (item) => {
|
|
16
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
17
|
-
const { styles } = useStyles();
|
|
18
|
-
return (
|
|
19
|
-
<LazyLoad className={styles.lazy}>
|
|
20
|
-
<AgentCardItem {...item} />
|
|
21
|
-
</LazyLoad>
|
|
22
|
-
);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
15
|
export interface AgentCardProps {
|
|
26
16
|
CardRender: FC<SpotlightCardProps>;
|
|
27
|
-
defaultAgents?: AgentsMarketIndexItem[];
|
|
28
17
|
mobile?: boolean;
|
|
29
18
|
}
|
|
30
19
|
|
|
31
|
-
const AgentCard = memo<AgentCardProps>(({
|
|
20
|
+
const AgentCard = memo<AgentCardProps>(({ CardRender, mobile }) => {
|
|
32
21
|
const { t } = useTranslation('market');
|
|
33
|
-
|
|
34
|
-
const { styles } = useStyles();
|
|
35
|
-
|
|
36
22
|
const [useFetchAgentList, keywords] = useMarketStore((s) => [
|
|
37
23
|
s.useFetchAgentList,
|
|
38
24
|
s.searchKeywords,
|
|
39
25
|
]);
|
|
40
|
-
useFetchAgentList();
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const agentList = clientAgentList.length === 0 && defaultAgents ? defaultAgents : clientAgentList;
|
|
26
|
+
const { isLoading } = useFetchAgentList();
|
|
27
|
+
const agentList = useMarketStore(agentMarketSelectors.getAgentList, isEqual);
|
|
28
|
+
const { styles } = useStyles();
|
|
44
29
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
const GridRender: SpotlightCardProps['renderItem'] = useCallback(
|
|
31
|
+
(props: any) => (
|
|
32
|
+
<LazyLoad className={styles.lazy}>
|
|
33
|
+
<AgentCardItem {...props} />
|
|
34
|
+
</LazyLoad>
|
|
35
|
+
),
|
|
36
|
+
[styles.lazy],
|
|
37
|
+
);
|
|
49
38
|
|
|
50
|
-
if (
|
|
39
|
+
if (isLoading) return <Loading />;
|
|
51
40
|
|
|
52
41
|
return (
|
|
53
42
|
<Flexbox gap={mobile ? 16 : 24}>
|
|
54
43
|
<TagList />
|
|
55
44
|
{keywords ? (
|
|
56
45
|
<CardRender
|
|
57
|
-
items={
|
|
58
|
-
renderItem={
|
|
46
|
+
items={agentList}
|
|
47
|
+
renderItem={GridRender}
|
|
59
48
|
spotlight={mobile ? undefined : false}
|
|
60
49
|
/>
|
|
61
50
|
) : (
|
|
62
51
|
<>
|
|
63
52
|
<div className={styles.subTitle}>{t('title.recentSubmits')}</div>
|
|
64
|
-
<CardRender items={
|
|
53
|
+
<CardRender items={agentList.slice(0, 3)} renderItem={GridRender} />
|
|
65
54
|
<div className={styles.subTitle}>{t('title.allAgents')}</div>
|
|
66
55
|
<CardRender
|
|
67
|
-
items={
|
|
68
|
-
renderItem={
|
|
56
|
+
items={agentList.slice(3)}
|
|
57
|
+
renderItem={GridRender}
|
|
69
58
|
spotlight={mobile ? undefined : false}
|
|
70
59
|
/>
|
|
71
60
|
</>
|
|
@@ -17,7 +17,6 @@ const { Link } = Typography;
|
|
|
17
17
|
const Header = memo(() => {
|
|
18
18
|
const { t } = useTranslation('market');
|
|
19
19
|
const { styles, theme } = useStyles();
|
|
20
|
-
|
|
21
20
|
const createSession = useSessionStore((s) => s.createSession);
|
|
22
21
|
const switchSideBar = useGlobalStore((s) => s.switchSideBar);
|
|
23
22
|
const agentItem = useMarketStore(agentMarketSelectors.currentAgentItem);
|
|
@@ -22,11 +22,10 @@ const AgentModalInner = memo(() => {
|
|
|
22
22
|
s.useFetchAgent,
|
|
23
23
|
s.currentIdentifier,
|
|
24
24
|
]);
|
|
25
|
-
const { styles } = useStyles();
|
|
26
|
-
|
|
27
|
-
const { data, isLoading } = useFetchAgent(currentIdentifier);
|
|
28
25
|
const { t } = useTranslation('market');
|
|
29
26
|
const [tab, setTab] = useState<string>(InfoTabs.prompt);
|
|
27
|
+
const { data, isLoading } = useFetchAgent(currentIdentifier);
|
|
28
|
+
const { styles } = useStyles();
|
|
30
29
|
|
|
31
30
|
if (isLoading || !data?.meta) return <Loading />;
|
|
32
31
|
|