@lobehub/chat 0.100.4 → 0.101.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.
- 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 +69 -0
- package/src/app/chat/(desktop)/features/ChatInput/index.tsx +60 -19
- package/src/app/chat/(mobile)/features/ChatInput/Mobile.tsx +79 -0
- package/src/app/chat/(mobile)/features/{ChatInput.tsx → ChatInput/index.tsx} +19 -3
- package/src/app/chat/(mobile)/features/ChatInput/style.mobile.ts +19 -0
- 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 +48 -0
- package/src/app/chat/features/ChatInput/ActionBar/FileUpload.tsx +53 -0
- package/src/app/chat/features/ChatInput/ActionBar/History.tsx +64 -0
- package/src/app/chat/features/ChatInput/ActionBar/ModelSwitch.tsx +42 -0
- package/src/app/chat/features/ChatInput/ActionBar/Temperature.tsx +47 -0
- package/src/app/chat/features/{ChatInputContent/ActionBar/Token.tsx → ChatInput/ActionBar/Token/TokenTag.tsx} +4 -4
- package/src/app/chat/features/ChatInput/ActionBar/Token/index.tsx +21 -0
- package/src/app/chat/features/ChatInput/ActionBar/config.ts +21 -0
- package/src/app/chat/features/ChatInput/ActionBar/index.tsx +35 -0
- package/src/app/chat/features/ChatInput/useSend.ts +21 -0
- 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/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/session/slices/agentConfig/selectors.ts +7 -1
- package/src/store/session/slices/chat/actions/message.ts +83 -18
- package/src/store/session/slices/chat/initialState.ts +4 -1
- 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/chat/selectors/chat.ts +3 -2
- 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/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 -35
- package/src/app/chat/features/ChatInputContent/ActionBar/ActionLeft.tsx +0 -117
- package/src/app/chat/features/ChatInputContent/ActionBar/ActionRight.tsx +0 -48
- package/src/app/chat/features/ChatInputContent/index.tsx +0 -71
- /package/src/app/chat/features/{ChatInputContent → ChatInput}/Topic/index.tsx +0 -0
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
// remove antd hash on test
|
|
3
|
+
import { theme } from 'antd';
|
|
4
|
+
// mock indexedDB to test with dexie
|
|
5
|
+
// refs: https://github.com/dumbmatter/fakeIndexedDB#dexie-and-other-indexeddb-api-wrappers
|
|
6
|
+
import 'fake-indexeddb/auto';
|
|
7
|
+
|
|
8
|
+
theme.defaultConfig.hashed = false;
|
package/vitest.config.ts
CHANGED
|
@@ -7,10 +7,12 @@ export default defineConfig({
|
|
|
7
7
|
'@': resolve(__dirname, './src'),
|
|
8
8
|
},
|
|
9
9
|
coverage: {
|
|
10
|
+
exclude: ['__mocks__/**'],
|
|
10
11
|
provider: 'v8',
|
|
11
12
|
reporter: ['text', 'json', 'lcov', 'text-summary'],
|
|
12
13
|
},
|
|
13
14
|
environment: 'jsdom',
|
|
14
15
|
globals: true,
|
|
16
|
+
setupFiles: './tests/setup.ts',
|
|
15
17
|
},
|
|
16
18
|
});
|
|
@@ -1,35 +0,0 @@
|
|
|
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 '../../../features/ChatInputContent/Topic';
|
|
9
|
-
|
|
10
|
-
const Footer = memo(() => {
|
|
11
|
-
const theme = useTheme();
|
|
12
|
-
const { t } = useTranslation('chat');
|
|
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;
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { ActionIcon, SliderWithInput } from '@lobehub/ui';
|
|
2
|
-
import { Dropdown, Popover, Switch } from 'antd';
|
|
3
|
-
import { BrainCog, Thermometer, Timer, TimerOff } from 'lucide-react';
|
|
4
|
-
import { memo } from 'react';
|
|
5
|
-
import { useTranslation } from 'react-i18next';
|
|
6
|
-
import { Flexbox } from 'react-layout-kit';
|
|
7
|
-
|
|
8
|
-
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
9
|
-
import { useSessionStore } from '@/store/session';
|
|
10
|
-
import { agentSelectors } from '@/store/session/selectors';
|
|
11
|
-
import { LanguageModel } from '@/types/llm';
|
|
12
|
-
|
|
13
|
-
const ActionLeft = memo(() => {
|
|
14
|
-
const { t } = useTranslation('setting');
|
|
15
|
-
|
|
16
|
-
const [model, temperature, historyCount, unlimited, updateAgentConfig] = useSessionStore((s) => {
|
|
17
|
-
const config = agentSelectors.currentAgentConfig(s);
|
|
18
|
-
return [
|
|
19
|
-
config.model,
|
|
20
|
-
config.params.temperature,
|
|
21
|
-
config.historyCount,
|
|
22
|
-
!config.enableHistoryCount,
|
|
23
|
-
s.updateAgentConfig,
|
|
24
|
-
];
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const modelList = useGlobalStore(settingsSelectors.modelList);
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<>
|
|
31
|
-
<Dropdown
|
|
32
|
-
menu={{
|
|
33
|
-
activeKey: model,
|
|
34
|
-
items: modelList.map((i) => ({ key: i, label: i })),
|
|
35
|
-
onClick: (e) => {
|
|
36
|
-
updateAgentConfig({ model: e.key as LanguageModel });
|
|
37
|
-
},
|
|
38
|
-
style: {
|
|
39
|
-
maxHeight: 400,
|
|
40
|
-
overflow: 'scroll',
|
|
41
|
-
},
|
|
42
|
-
}}
|
|
43
|
-
trigger={['click']}
|
|
44
|
-
>
|
|
45
|
-
<ActionIcon icon={BrainCog} placement={'bottom'} title={t('settingModel.model.title')} />
|
|
46
|
-
</Dropdown>
|
|
47
|
-
<Popover
|
|
48
|
-
arrow={false}
|
|
49
|
-
content={
|
|
50
|
-
<SliderWithInput
|
|
51
|
-
controls={false}
|
|
52
|
-
max={1}
|
|
53
|
-
min={0}
|
|
54
|
-
onChange={(v) => {
|
|
55
|
-
updateAgentConfig({ params: { temperature: v } });
|
|
56
|
-
}}
|
|
57
|
-
size={'small'}
|
|
58
|
-
step={0.1}
|
|
59
|
-
style={{ width: 160 }}
|
|
60
|
-
value={temperature}
|
|
61
|
-
/>
|
|
62
|
-
}
|
|
63
|
-
placement={'top'}
|
|
64
|
-
trigger={'click'}
|
|
65
|
-
>
|
|
66
|
-
<ActionIcon
|
|
67
|
-
icon={Thermometer}
|
|
68
|
-
placement={'bottom'}
|
|
69
|
-
title={t('settingModel.temperature.titleWithValue', { value: temperature })}
|
|
70
|
-
/>
|
|
71
|
-
</Popover>
|
|
72
|
-
<Popover
|
|
73
|
-
arrow={false}
|
|
74
|
-
content={
|
|
75
|
-
<Flexbox align={'center'} gap={16} horizontal>
|
|
76
|
-
<SliderWithInput
|
|
77
|
-
disabled={unlimited}
|
|
78
|
-
max={30}
|
|
79
|
-
min={1}
|
|
80
|
-
onChange={(v) => {
|
|
81
|
-
updateAgentConfig({ historyCount: v });
|
|
82
|
-
}}
|
|
83
|
-
step={1}
|
|
84
|
-
style={{ width: 160 }}
|
|
85
|
-
value={historyCount}
|
|
86
|
-
/>
|
|
87
|
-
<Flexbox align={'center'} gap={4} horizontal>
|
|
88
|
-
<Switch
|
|
89
|
-
checked={unlimited}
|
|
90
|
-
onChange={(checked) => {
|
|
91
|
-
updateAgentConfig({ enableHistoryCount: !checked });
|
|
92
|
-
}}
|
|
93
|
-
size={'small'}
|
|
94
|
-
/>
|
|
95
|
-
{t('settingChat.enableHistoryCount.alias')}
|
|
96
|
-
</Flexbox>
|
|
97
|
-
</Flexbox>
|
|
98
|
-
}
|
|
99
|
-
placement={'top'}
|
|
100
|
-
trigger={'click'}
|
|
101
|
-
>
|
|
102
|
-
<ActionIcon
|
|
103
|
-
icon={unlimited ? TimerOff : Timer}
|
|
104
|
-
placement={'bottom'}
|
|
105
|
-
title={t(
|
|
106
|
-
unlimited
|
|
107
|
-
? 'settingChat.enableHistoryCount.unlimited'
|
|
108
|
-
: 'settingChat.enableHistoryCount.limited',
|
|
109
|
-
{ number: historyCount || 0 },
|
|
110
|
-
)}
|
|
111
|
-
/>
|
|
112
|
-
</Popover>
|
|
113
|
-
</>
|
|
114
|
-
);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
export default ActionLeft;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { ActionIcon } from '@lobehub/ui';
|
|
2
|
-
import { Popconfirm } from 'antd';
|
|
3
|
-
import { useResponsive } from 'antd-style';
|
|
4
|
-
import { Eraser } from 'lucide-react';
|
|
5
|
-
import { memo } from 'react';
|
|
6
|
-
import { useHotkeys } from 'react-hotkeys-hook';
|
|
7
|
-
import { useTranslation } from 'react-i18next';
|
|
8
|
-
|
|
9
|
-
import HotKeys from '@/components/HotKeys';
|
|
10
|
-
import { CLEAN_MESSAGE_KEY, PREFIX_KEY } from '@/const/hotkeys';
|
|
11
|
-
import { useSessionStore } from '@/store/session';
|
|
12
|
-
|
|
13
|
-
import SaveTopic from '../Topic';
|
|
14
|
-
|
|
15
|
-
const ActionsRight = memo(() => {
|
|
16
|
-
const { t } = useTranslation('setting');
|
|
17
|
-
const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig]);
|
|
18
|
-
const { mobile } = useResponsive();
|
|
19
|
-
const hotkeys = [PREFIX_KEY, CLEAN_MESSAGE_KEY].join('+');
|
|
20
|
-
|
|
21
|
-
useHotkeys(hotkeys, clearMessage, {
|
|
22
|
-
preventDefault: true,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<>
|
|
27
|
-
{mobile && <SaveTopic />}
|
|
28
|
-
<Popconfirm
|
|
29
|
-
cancelText={t('cancel', { ns: 'common' })}
|
|
30
|
-
okButtonProps={{ danger: true }}
|
|
31
|
-
okText={t('ok', { ns: 'common' })}
|
|
32
|
-
onConfirm={() => clearMessage()}
|
|
33
|
-
placement={'topRight'}
|
|
34
|
-
title={t('confirmClearCurrentMessages', { ns: 'chat' })}
|
|
35
|
-
>
|
|
36
|
-
<ActionIcon
|
|
37
|
-
icon={Eraser}
|
|
38
|
-
placement={'bottom'}
|
|
39
|
-
title={
|
|
40
|
-
(<HotKeys desc={t('clearCurrentMessages', { ns: 'chat' })} keys={hotkeys} />) as any
|
|
41
|
-
}
|
|
42
|
-
/>
|
|
43
|
-
</Popconfirm>
|
|
44
|
-
</>
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export default ActionsRight;
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { ChatInputArea } from '@lobehub/ui';
|
|
2
|
-
import { useResponsive } from 'antd-style';
|
|
3
|
-
import dynamic from 'next/dynamic';
|
|
4
|
-
import { ReactNode, Suspense, memo, useState } from 'react';
|
|
5
|
-
import { useTranslation } from 'react-i18next';
|
|
6
|
-
|
|
7
|
-
import { CHAT_TEXTAREA_HEIGHT } from '@/const/layoutTokens';
|
|
8
|
-
import { useSessionStore } from '@/store/session';
|
|
9
|
-
import { agentSelectors } from '@/store/session/selectors';
|
|
10
|
-
|
|
11
|
-
import ActionLeft from './ActionBar/ActionLeft';
|
|
12
|
-
import ActionsRight from './ActionBar/ActionRight';
|
|
13
|
-
|
|
14
|
-
const Token = dynamic(() => import('./ActionBar/Token'), { ssr: false });
|
|
15
|
-
|
|
16
|
-
interface ChatContentProps {
|
|
17
|
-
expand?: boolean;
|
|
18
|
-
footer?: ReactNode;
|
|
19
|
-
mobile?: boolean;
|
|
20
|
-
onExpandChange?: (expand: boolean) => void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const ChatInputContent = memo<ChatContentProps>(
|
|
24
|
-
({ expand, onExpandChange, mobile: defaultMobile, footer }) => {
|
|
25
|
-
const { t } = useTranslation('common');
|
|
26
|
-
|
|
27
|
-
const [message, setMessage] = useState('');
|
|
28
|
-
const { mobile: runtimeMobile } = useResponsive();
|
|
29
|
-
|
|
30
|
-
const mobile = runtimeMobile || defaultMobile;
|
|
31
|
-
|
|
32
|
-
const [isLoading, sendMessage, stopGenerateMessage, showTokenTag] = useSessionStore((s) => [
|
|
33
|
-
!!s.chatLoadingId,
|
|
34
|
-
s.sendMessage,
|
|
35
|
-
s.stopGenerateMessage,
|
|
36
|
-
agentSelectors.showTokenTag(s),
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<ChatInputArea
|
|
41
|
-
actions={
|
|
42
|
-
<>
|
|
43
|
-
<ActionLeft />
|
|
44
|
-
{showTokenTag && (
|
|
45
|
-
<Suspense>
|
|
46
|
-
<Token input={message} />
|
|
47
|
-
</Suspense>
|
|
48
|
-
)}
|
|
49
|
-
</>
|
|
50
|
-
}
|
|
51
|
-
actionsRight={<ActionsRight />}
|
|
52
|
-
expand={expand}
|
|
53
|
-
footer={footer}
|
|
54
|
-
loading={isLoading}
|
|
55
|
-
minHeight={mobile ? 0 : CHAT_TEXTAREA_HEIGHT}
|
|
56
|
-
onExpandChange={onExpandChange}
|
|
57
|
-
onInputChange={setMessage}
|
|
58
|
-
onSend={sendMessage}
|
|
59
|
-
onStop={stopGenerateMessage}
|
|
60
|
-
placeholder={t('sendPlaceholder', { ns: 'chat' })}
|
|
61
|
-
text={{
|
|
62
|
-
send: t('send'),
|
|
63
|
-
stop: t('stop'),
|
|
64
|
-
}}
|
|
65
|
-
value={message}
|
|
66
|
-
/>
|
|
67
|
-
);
|
|
68
|
-
},
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
export default ChatInputContent;
|
|
File without changes
|