@fe-free/ai 4.1.18 → 4.1.20
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 +18 -0
- package/package.json +4 -4
- package/src/ai.stories.tsx +1 -1
- package/src/helper.tsx +21 -1
- package/src/m_sender/actions.tsx +1 -1
- package/src/m_sender/index.tsx +1 -1
- package/src/messages/messages.tsx +79 -10
- package/src/sender/actions.tsx +9 -45
- package/src/sender/files.tsx +2 -2
- package/src/sender/index.tsx +74 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @fe-free/ai
|
|
2
2
|
|
|
3
|
+
## 4.1.20
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat: ai
|
|
8
|
+
- @fe-free/core@4.1.20
|
|
9
|
+
- @fe-free/icons@4.1.20
|
|
10
|
+
- @fe-free/tool@4.1.20
|
|
11
|
+
|
|
12
|
+
## 4.1.19
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- feat: ai
|
|
17
|
+
- @fe-free/core@4.1.19
|
|
18
|
+
- @fe-free/icons@4.1.19
|
|
19
|
+
- @fe-free/tool@4.1.19
|
|
20
|
+
|
|
3
21
|
## 4.1.18
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fe-free/ai",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.20",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"author": "",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"lodash-es": "^4.17.21",
|
|
20
20
|
"uuid": "^13.0.0",
|
|
21
21
|
"zustand": "^4.5.7",
|
|
22
|
-
"@fe-free/core": "4.1.
|
|
22
|
+
"@fe-free/core": "4.1.20"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"antd": "^5.27.1",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"i18next-icu": "^2.4.1",
|
|
30
30
|
"react": "^19.2.0",
|
|
31
31
|
"react-i18next": "^16.4.0",
|
|
32
|
-
"@fe-free/icons": "4.1.
|
|
33
|
-
"@fe-free/tool": "4.1.
|
|
32
|
+
"@fe-free/icons": "4.1.20",
|
|
33
|
+
"@fe-free/tool": "4.1.20"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"test": "echo \"Error: no test specified\" && exit 1",
|
package/src/ai.stories.tsx
CHANGED
package/src/helper.tsx
CHANGED
|
@@ -42,4 +42,24 @@ function generateUUID() {
|
|
|
42
42
|
return uuidv4();
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
function getScrollbarWidth() {
|
|
46
|
+
// 创建一个不可见的 div 元素
|
|
47
|
+
const outer = document.createElement('div');
|
|
48
|
+
outer.style.visibility = 'hidden';
|
|
49
|
+
outer.style.overflow = 'scroll'; // 强制显示滚动条
|
|
50
|
+
document.body.appendChild(outer);
|
|
51
|
+
|
|
52
|
+
// 创建一个内部 div,宽度为 100%
|
|
53
|
+
const inner = document.createElement('div');
|
|
54
|
+
outer.appendChild(inner);
|
|
55
|
+
|
|
56
|
+
// 滚动条宽度 = 外部容器的宽度 - 内部内容的宽度
|
|
57
|
+
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
|
|
58
|
+
|
|
59
|
+
// 清理 DOM
|
|
60
|
+
document.body.removeChild(outer);
|
|
61
|
+
|
|
62
|
+
return scrollbarWidth;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { generateUUID, getScrollbarWidth, RecordLoading };
|
package/src/m_sender/actions.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import type { MSenderProps } from './types';
|
|
|
7
7
|
|
|
8
8
|
function Actions(
|
|
9
9
|
props: MSenderProps & {
|
|
10
|
-
refText: RefObject<HTMLTextAreaElement>;
|
|
10
|
+
refText: RefObject<HTMLTextAreaElement | null>;
|
|
11
11
|
type: 'input' | 'record';
|
|
12
12
|
setType: (type: 'input' | 'record') => void;
|
|
13
13
|
},
|
package/src/m_sender/index.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import { Actions } from './actions';
|
|
|
6
6
|
import { RecordAction } from './record';
|
|
7
7
|
import type { MSenderProps, MSenderRef } from './types';
|
|
8
8
|
|
|
9
|
-
function Text(props: MSenderProps & { refText: RefObject<HTMLTextAreaElement> }) {
|
|
9
|
+
function Text(props: MSenderProps & { refText: RefObject<HTMLTextAreaElement | null> }) {
|
|
10
10
|
const { value, onChange, placeholder, refText, autoFocus } = props;
|
|
11
11
|
|
|
12
12
|
return (
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { PageLayout } from '@fe-free/core';
|
|
2
|
-
import {
|
|
2
|
+
import { AngleLeftOutlined } from '@fe-free/icons';
|
|
3
|
+
import { useMemoizedFn } from 'ahooks';
|
|
4
|
+
import { Button } from 'antd';
|
|
5
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { getScrollbarWidth } from '../helper';
|
|
3
7
|
import { EnumChatMessageType, type ChatMessage } from '../store/types';
|
|
4
8
|
|
|
5
9
|
interface MessagesProps<AIData> {
|
|
6
|
-
refList?: React.RefObject<HTMLDivElement>;
|
|
10
|
+
refList?: React.RefObject<HTMLDivElement | null>;
|
|
7
11
|
messages?: ChatMessage<AIData>[];
|
|
8
12
|
/** 含所有 */
|
|
9
13
|
renderMessage?: (props: { message: ChatMessage<AIData> }) => React.ReactNode;
|
|
@@ -15,6 +19,37 @@ interface MessagesProps<AIData> {
|
|
|
15
19
|
renderMessageOfAI?: (props: { message: ChatMessage<AIData> }) => React.ReactNode;
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
function useScrollWidth() {
|
|
23
|
+
const width = useMemo(() => {
|
|
24
|
+
return getScrollbarWidth();
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return width;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function useScrollToBottom({ ref }) {
|
|
31
|
+
const [isNearBottom, setIsNearBottom] = useState(false);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!ref.current) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const handleScroll = () => {
|
|
39
|
+
const isNearBottom =
|
|
40
|
+
ref.current?.scrollTop + ref.current?.clientHeight >= ref.current?.scrollHeight - 200;
|
|
41
|
+
setIsNearBottom(isNearBottom);
|
|
42
|
+
};
|
|
43
|
+
ref.current.addEventListener('scroll', handleScroll);
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
ref.current.removeEventListener('scroll', handleScroll);
|
|
47
|
+
};
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
return isNearBottom;
|
|
51
|
+
}
|
|
52
|
+
|
|
18
53
|
function Messages<AIData>(props: MessagesProps<AIData>) {
|
|
19
54
|
const {
|
|
20
55
|
refList,
|
|
@@ -32,8 +67,7 @@ function Messages<AIData>(props: MessagesProps<AIData>) {
|
|
|
32
67
|
return messages?.[messages.length - 1];
|
|
33
68
|
}, [messages]);
|
|
34
69
|
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
70
|
+
const scrollToBottom = useMemoizedFn(() => {
|
|
37
71
|
if (!lastMessage?.uuid) {
|
|
38
72
|
return;
|
|
39
73
|
}
|
|
@@ -42,10 +76,15 @@ function Messages<AIData>(props: MessagesProps<AIData>) {
|
|
|
42
76
|
setTimeout(() => {
|
|
43
77
|
const element = document.querySelector(`[data-uuid="${lastMessage.uuid}"]`);
|
|
44
78
|
if (element) {
|
|
45
|
-
element.scrollIntoView({ behavior: 'smooth' });
|
|
79
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
46
80
|
}
|
|
47
81
|
}, 100);
|
|
48
|
-
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 首次和更新时滚动到最新消息
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
scrollToBottom();
|
|
87
|
+
}, [scrollToBottom]);
|
|
49
88
|
|
|
50
89
|
// 数据更新是,如果 dom 处于可视区域,则滚动
|
|
51
90
|
useEffect(() => {
|
|
@@ -66,17 +105,34 @@ function Messages<AIData>(props: MessagesProps<AIData>) {
|
|
|
66
105
|
// 如果最后一个元素可见,则滚动到底部
|
|
67
106
|
const isVisible = top < listBottom && bottom > listTop;
|
|
68
107
|
if (isVisible) {
|
|
69
|
-
|
|
108
|
+
scrollToBottom();
|
|
70
109
|
}
|
|
71
110
|
}, 100);
|
|
72
|
-
}, [lastMessage?.updatedAt, lastMessage?.uuid, ref]);
|
|
111
|
+
}, [lastMessage?.updatedAt, lastMessage?.uuid, ref, scrollToBottom]);
|
|
112
|
+
|
|
113
|
+
const scrollWidth = useScrollWidth();
|
|
114
|
+
|
|
115
|
+
const isNearBottom = useScrollToBottom({ ref });
|
|
73
116
|
|
|
74
117
|
return (
|
|
75
118
|
<PageLayout>
|
|
76
|
-
<div
|
|
119
|
+
<div
|
|
120
|
+
ref={ref}
|
|
121
|
+
className="fea-messages-scroll relative flex h-full flex-col overflow-y-auto overflow-x-hidden"
|
|
122
|
+
style={{
|
|
123
|
+
transform: `translateZ(0)`,
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
77
126
|
{messages?.map((message) => {
|
|
78
127
|
return (
|
|
79
|
-
<div
|
|
128
|
+
<div
|
|
129
|
+
key={message.uuid}
|
|
130
|
+
data-uuid={message.uuid}
|
|
131
|
+
className="flex flex-col"
|
|
132
|
+
style={{
|
|
133
|
+
marginRight: `-${scrollWidth}px`,
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
80
136
|
{renderMessage ? (
|
|
81
137
|
renderMessage?.({ message })
|
|
82
138
|
) : (
|
|
@@ -97,6 +153,19 @@ function Messages<AIData>(props: MessagesProps<AIData>) {
|
|
|
97
153
|
</div>
|
|
98
154
|
);
|
|
99
155
|
})}
|
|
156
|
+
<div className="sticky bottom-2 left-0 right-0 flex justify-center">
|
|
157
|
+
<Button
|
|
158
|
+
shape="circle"
|
|
159
|
+
icon={<AngleLeftOutlined rotate={-90} />}
|
|
160
|
+
onClick={() => {
|
|
161
|
+
scrollToBottom();
|
|
162
|
+
}}
|
|
163
|
+
className="bg-white shadow-lg"
|
|
164
|
+
style={{
|
|
165
|
+
transform: `translateY(${isNearBottom ? 30 : 0}px) scale(${isNearBottom ? 0.1 : 1})`,
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
</div>
|
|
100
169
|
</div>
|
|
101
170
|
</PageLayout>
|
|
102
171
|
);
|
package/src/sender/actions.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Icons from '@fe-free/icons';
|
|
2
2
|
import type { UploadFile } from 'antd';
|
|
3
|
-
import { Button
|
|
4
|
-
import {
|
|
3
|
+
import { Button } from 'antd';
|
|
4
|
+
import type { RefObject } from 'react';
|
|
5
5
|
import SendIcon from '../svgs/send.svg?react';
|
|
6
6
|
import { FileAction } from './files';
|
|
7
7
|
import { RecordAction } from './record';
|
|
@@ -9,65 +9,29 @@ import type { SenderProps } from './types';
|
|
|
9
9
|
|
|
10
10
|
function Actions(
|
|
11
11
|
props: SenderProps & {
|
|
12
|
-
refText: RefObject<HTMLTextAreaElement>;
|
|
13
|
-
refUpload: RefObject<HTMLDivElement>;
|
|
12
|
+
refText: RefObject<HTMLTextAreaElement | null>;
|
|
13
|
+
refUpload: RefObject<HTMLDivElement | null>;
|
|
14
14
|
isUploading: boolean;
|
|
15
15
|
fileList: UploadFile[];
|
|
16
16
|
setFileList: (fileList: UploadFile[]) => void;
|
|
17
17
|
fileUrls: string[];
|
|
18
18
|
setFileUrls: (fileUrls: string[]) => void;
|
|
19
|
+
onSubmit: () => Promise<void>;
|
|
19
20
|
},
|
|
20
21
|
) {
|
|
21
22
|
const {
|
|
22
|
-
refText,
|
|
23
|
-
loading,
|
|
24
|
-
onSubmit,
|
|
25
|
-
value,
|
|
26
|
-
onChange,
|
|
27
23
|
refUpload,
|
|
28
24
|
isUploading,
|
|
29
|
-
setFileList,
|
|
30
25
|
fileUrls,
|
|
31
26
|
setFileUrls,
|
|
32
27
|
allowUpload,
|
|
33
28
|
allowSpeech,
|
|
29
|
+
loading,
|
|
30
|
+
onSubmit,
|
|
34
31
|
} = props;
|
|
35
32
|
|
|
36
33
|
const isLoading = loading || isUploading;
|
|
37
34
|
|
|
38
|
-
const handleSubmit = useCallback(async () => {
|
|
39
|
-
if (isLoading || allowSpeech?.recording) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const newValue = {
|
|
44
|
-
...value,
|
|
45
|
-
text: value?.text?.trim(),
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// 有内容才提交
|
|
49
|
-
if (newValue.text || (newValue.files && newValue.files.length > 0)) {
|
|
50
|
-
await Promise.resolve(onSubmit?.(newValue));
|
|
51
|
-
|
|
52
|
-
// reset
|
|
53
|
-
setFileList([]);
|
|
54
|
-
setFileUrls([]);
|
|
55
|
-
onChange?.({});
|
|
56
|
-
|
|
57
|
-
// focus
|
|
58
|
-
refText.current?.focus();
|
|
59
|
-
}
|
|
60
|
-
}, [
|
|
61
|
-
isLoading,
|
|
62
|
-
allowSpeech?.recording,
|
|
63
|
-
value,
|
|
64
|
-
onSubmit,
|
|
65
|
-
setFileList,
|
|
66
|
-
setFileUrls,
|
|
67
|
-
onChange,
|
|
68
|
-
refText,
|
|
69
|
-
]);
|
|
70
|
-
|
|
71
35
|
return (
|
|
72
36
|
<div className="flex items-center gap-2">
|
|
73
37
|
<div className="flex flex-1 gap-1">
|
|
@@ -80,7 +44,7 @@ function Actions(
|
|
|
80
44
|
/>
|
|
81
45
|
)}
|
|
82
46
|
</div>
|
|
83
|
-
<Divider type="vertical" />
|
|
47
|
+
{/* <Divider type="vertical" /> */}
|
|
84
48
|
<div className="flex items-center gap-2">
|
|
85
49
|
{allowSpeech && <RecordAction {...props} />}
|
|
86
50
|
<Button
|
|
@@ -89,7 +53,7 @@ function Actions(
|
|
|
89
53
|
icon={<Icons component={SendIcon} className="!text-lg" />}
|
|
90
54
|
loading={isLoading}
|
|
91
55
|
// disabled={loading}
|
|
92
|
-
onClick={
|
|
56
|
+
onClick={onSubmit}
|
|
93
57
|
/>
|
|
94
58
|
</div>
|
|
95
59
|
</div>
|
package/src/sender/files.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import type { SenderProps } from './types';
|
|
|
11
11
|
|
|
12
12
|
function FileAction(
|
|
13
13
|
props: SenderProps & {
|
|
14
|
-
refUpload: RefObject<HTMLDivElement>;
|
|
14
|
+
refUpload: RefObject<HTMLDivElement | null>;
|
|
15
15
|
fileUrls: string[];
|
|
16
16
|
setFileUrls: (fileUrls: string[]) => void;
|
|
17
17
|
},
|
|
@@ -89,7 +89,7 @@ function FileAction(
|
|
|
89
89
|
|
|
90
90
|
function FileUpload(
|
|
91
91
|
props: SenderProps & {
|
|
92
|
-
refUpload: RefObject<HTMLDivElement>;
|
|
92
|
+
refUpload: RefObject<HTMLDivElement | null>;
|
|
93
93
|
fileList: UploadFile[];
|
|
94
94
|
setFileList: (fileList: UploadFile[]) => void;
|
|
95
95
|
uploadMaxCount?: number;
|
package/src/sender/index.tsx
CHANGED
|
@@ -9,8 +9,29 @@ import { Actions } from './actions';
|
|
|
9
9
|
import { FileUpload, Files } from './files';
|
|
10
10
|
import type { SenderProps, SenderRef } from './types';
|
|
11
11
|
|
|
12
|
-
function Text(
|
|
13
|
-
|
|
12
|
+
function Text(
|
|
13
|
+
props: SenderProps & {
|
|
14
|
+
refText: RefObject<HTMLTextAreaElement | null>;
|
|
15
|
+
onSubmit?: () => void;
|
|
16
|
+
},
|
|
17
|
+
) {
|
|
18
|
+
const { value, onChange, placeholder, refText, onSubmit } = props;
|
|
19
|
+
|
|
20
|
+
const handleKeyDown = useCallback(
|
|
21
|
+
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
22
|
+
// Shift + Enter: 换行(默认行为)
|
|
23
|
+
if (e.key === 'Enter' && e.shiftKey) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Enter: 提交
|
|
28
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
onSubmit?.();
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
[onSubmit],
|
|
34
|
+
);
|
|
14
35
|
|
|
15
36
|
return (
|
|
16
37
|
<Input.TextArea
|
|
@@ -19,6 +40,7 @@ function Text(props: SenderProps & { refText: RefObject<HTMLTextAreaElement> })
|
|
|
19
40
|
onChange={(e) => {
|
|
20
41
|
onChange?.({ ...value, text: e.target.value });
|
|
21
42
|
}}
|
|
43
|
+
onKeyDown={handleKeyDown}
|
|
22
44
|
placeholder={placeholder}
|
|
23
45
|
autoSize={{ minRows: 2, maxRows: 8 }}
|
|
24
46
|
className="mb-1 px-1 py-0"
|
|
@@ -32,21 +54,22 @@ function Sender(originProps: SenderProps) {
|
|
|
32
54
|
const props = useMemo(() => {
|
|
33
55
|
return {
|
|
34
56
|
placeholder:
|
|
35
|
-
originProps.placeholder ??
|
|
57
|
+
originProps.placeholder ??
|
|
58
|
+
t('@fe-free/ai.sender.describeYourQuestion', '描述你的问题, shift + enter 换行'),
|
|
36
59
|
...originProps,
|
|
37
60
|
};
|
|
38
61
|
}, [originProps, t]);
|
|
39
62
|
|
|
40
63
|
const refText = useRef<HTMLTextAreaElement>(null);
|
|
41
64
|
|
|
42
|
-
const { value, onChange, allowUpload } = props;
|
|
65
|
+
const { value, onChange, allowUpload, onSubmit, loading, allowSpeech } = props;
|
|
43
66
|
const { filesMaxCount } = allowUpload || {};
|
|
44
67
|
|
|
45
68
|
const refContainer = useRef<HTMLDivElement>(null);
|
|
46
69
|
const refUpload = useRef<HTMLDivElement>(null);
|
|
47
70
|
const [dragHover, setDragHover] = useState(false);
|
|
48
71
|
|
|
49
|
-
useDrop(refContainer, {
|
|
72
|
+
useDrop(allowUpload ? refContainer : null, {
|
|
50
73
|
onDragEnter: () => {
|
|
51
74
|
setDragHover(true);
|
|
52
75
|
},
|
|
@@ -94,6 +117,41 @@ function Sender(originProps: SenderProps) {
|
|
|
94
117
|
return fileList.some((file) => !file.response?.data?.url);
|
|
95
118
|
}, [fileList]);
|
|
96
119
|
|
|
120
|
+
const handleSubmit = useCallback(async () => {
|
|
121
|
+
const isLoading = loading || isUploading;
|
|
122
|
+
|
|
123
|
+
if (isLoading || allowSpeech?.recording) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const newValue = {
|
|
128
|
+
...value,
|
|
129
|
+
text: value?.text?.trim(),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// 有内容才提交
|
|
133
|
+
if (newValue.text || (newValue.files && newValue.files.length > 0)) {
|
|
134
|
+
await Promise.resolve(onSubmit?.(newValue));
|
|
135
|
+
|
|
136
|
+
// reset
|
|
137
|
+
setFileList([]);
|
|
138
|
+
setFileUrls([]);
|
|
139
|
+
onChange?.({});
|
|
140
|
+
|
|
141
|
+
// focus
|
|
142
|
+
refText.current?.focus();
|
|
143
|
+
}
|
|
144
|
+
}, [
|
|
145
|
+
loading,
|
|
146
|
+
isUploading,
|
|
147
|
+
allowSpeech?.recording,
|
|
148
|
+
value,
|
|
149
|
+
onSubmit,
|
|
150
|
+
setFileList,
|
|
151
|
+
setFileUrls,
|
|
152
|
+
onChange,
|
|
153
|
+
]);
|
|
154
|
+
|
|
97
155
|
return (
|
|
98
156
|
<div className="fea-sender-wrap">
|
|
99
157
|
<div
|
|
@@ -116,7 +174,7 @@ function Sender(originProps: SenderProps) {
|
|
|
116
174
|
setFileUrls={setFileUrls}
|
|
117
175
|
/>
|
|
118
176
|
<div className="flex">
|
|
119
|
-
<Text {...props} refText={refText} />
|
|
177
|
+
<Text {...props} refText={refText} onSubmit={handleSubmit} />
|
|
120
178
|
</div>
|
|
121
179
|
<Actions
|
|
122
180
|
{...props}
|
|
@@ -127,14 +185,17 @@ function Sender(originProps: SenderProps) {
|
|
|
127
185
|
setFileList={setFileList}
|
|
128
186
|
fileUrls={fileUrls}
|
|
129
187
|
setFileUrls={setFileUrls}
|
|
188
|
+
onSubmit={handleSubmit}
|
|
130
189
|
/>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
190
|
+
{allowUpload && (
|
|
191
|
+
<FileUpload
|
|
192
|
+
{...props}
|
|
193
|
+
refUpload={refUpload}
|
|
194
|
+
fileList={fileList}
|
|
195
|
+
setFileList={setFileList}
|
|
196
|
+
uploadMaxCount={filesMaxCount ? filesMaxCount - fileUrls.length : undefined}
|
|
197
|
+
/>
|
|
198
|
+
)}
|
|
138
199
|
</div>
|
|
139
200
|
<div className="mt-1 text-center text-xs text-03">
|
|
140
201
|
{t(
|