11import { useSpeech } from '@/web/common/hooks/useSpeech' ;
22import { useSystemStore } from '@/web/common/system/useSystemStore' ;
3- import { Box , Flex , Spinner , Textarea } from '@chakra-ui/react' ;
4- import React , { useRef , useEffect } from 'react' ;
3+ import { Box , Flex , Image , Spinner , Textarea } from '@chakra-ui/react' ;
4+ import React , { useRef , useEffect , useCallback , useState , useMemo } from 'react' ;
55import { useTranslation } from 'react-i18next' ;
66import MyTooltip from '../MyTooltip' ;
77import MyIcon from '../Icon' ;
88import styles from './index.module.scss' ;
99import { useRouter } from 'next/router' ;
10+ import { useSelectFile } from '@/web/common/file/hooks/useSelectFile' ;
11+ import { compressImgAndUpload } from '@/web/common/file/controller' ;
12+ import { useToast } from '@/web/common/hooks/useToast' ;
1013
1114const MessageInput = ( {
1215 onChange,
@@ -38,6 +41,60 @@ const MessageInput = ({
3841 const { t } = useTranslation ( ) ;
3942 const textareaMinH = '22px' ;
4043 const havInput = ! ! TextareaDom . current ?. value ;
44+ const { toast } = useToast ( ) ;
45+ const [ imgBase64Array , setImgBase64Array ] = useState < string [ ] > ( [ ] ) ;
46+ const [ fileList , setFileList ] = useState < File [ ] > ( [ ] ) ;
47+ const [ imgSrcArray , setImgSrcArray ] = useState < string [ ] > ( [ ] ) ;
48+
49+ const { File, onOpen : onOpenSelectFile } = useSelectFile ( {
50+ fileType : '.jpg,.png' ,
51+ multiple : true
52+ } ) ;
53+
54+ useEffect ( ( ) => {
55+ fileList . forEach ( ( file ) => {
56+ const reader = new FileReader ( ) ;
57+ reader . readAsDataURL ( file ) ;
58+ reader . onload = async ( ) => {
59+ setImgBase64Array ( ( prev ) => [ ...prev , reader . result as string ] ) ;
60+ } ;
61+ } ) ;
62+ } , [ fileList ] ) ;
63+
64+ const onSelectFile = useCallback ( ( e : File [ ] ) => {
65+ if ( ! e || e . length === 0 ) {
66+ return ;
67+ }
68+ setFileList ( e ) ;
69+ } , [ ] ) ;
70+
71+ const handleSend = useCallback ( async ( ) => {
72+ try {
73+ for ( const file of fileList ) {
74+ const src = await compressImgAndUpload ( {
75+ file,
76+ maxW : 1000 ,
77+ maxH : 1000 ,
78+ maxSize : 1024 * 1024 * 2
79+ } ) ;
80+ imgSrcArray . push ( src ) ;
81+ }
82+ } catch ( err : any ) {
83+ toast ( {
84+ title : typeof err === 'string' ? err : '文件上传异常' ,
85+ status : 'warning'
86+ } ) ;
87+ }
88+
89+ const textareaValue = TextareaDom . current ?. value || '' ;
90+ const inputMessage =
91+ imgSrcArray . length === 0
92+ ? textareaValue
93+ : `\`\`\`img-block\n${ JSON . stringify ( imgSrcArray ) } \n\`\`\`\n${ textareaValue } ` ;
94+ onSendMessage ( inputMessage ) ;
95+ setImgBase64Array ( [ ] ) ;
96+ setImgSrcArray ( [ ] ) ;
97+ } , [ TextareaDom , fileList , imgSrcArray , onSendMessage , toast ] ) ;
4198
4299 useEffect ( ( ) => {
43100 if ( ! stream ) {
@@ -60,7 +117,7 @@ const MessageInput = ({
60117 < >
61118 < Box m = { [ '0 auto' , '10px auto' ] } w = { '100%' } maxW = { [ 'auto' , 'min(800px, 100%)' ] } px = { [ 0 , 5 ] } >
62119 < Box
63- py = { '18px' }
120+ py = { imgBase64Array . length > 0 ? '8px' : '18px' }
64121 position = { 'relative' }
65122 boxShadow = { isSpeaking ? `0 0 10px rgba(54,111,255,0.4)` : `0 0 10px rgba(0,0,0,0.2)` }
66123 { ...( isPc
@@ -93,11 +150,74 @@ const MessageInput = ({
93150 < Spinner size = { 'sm' } mr = { 4 } />
94151 { t ( 'chat.Converting to text' ) }
95152 </ Box >
153+ { /* file uploader */ }
154+ < Flex
155+ position = { 'absolute' }
156+ alignItems = { 'center' }
157+ left = { [ '12px' , '14px' ] }
158+ bottom = { [ '15px' , '13px' ] }
159+ h = { [ '26px' , '32px' ] }
160+ zIndex = { 10 }
161+ cursor = { 'pointer' }
162+ onClick = { onOpenSelectFile }
163+ >
164+ < MyTooltip label = { t ( 'core.chat.Select File' ) } >
165+ < MyIcon name = { 'core/chat/fileSelect' } />
166+ </ MyTooltip >
167+ < File onSelect = { onSelectFile } />
168+ </ Flex >
169+ { /* file preview */ }
170+ < Flex w = { '96%' } wrap = { 'wrap' } ml = { 4 } >
171+ { imgBase64Array . length > 0 &&
172+ imgBase64Array . map ( ( src , index ) => (
173+ < Box
174+ key = { index }
175+ border = { '1px solid rgba(0,0,0,0.12)' }
176+ mr = { 2 }
177+ mb = { 2 }
178+ rounded = { 'md' }
179+ position = { 'relative' }
180+ _hover = { {
181+ '.close-icon' : { display : 'block' }
182+ } }
183+ >
184+ < MyIcon
185+ name = { 'closeSolid' }
186+ w = { '16px' }
187+ h = { '16px' }
188+ color = { 'myGray.700' }
189+ cursor = { 'pointer' }
190+ _hover = { { color : 'myBlue.600' } }
191+ position = { 'absolute' }
192+ right = { - 2 }
193+ top = { - 2 }
194+ onClick = { ( ) => {
195+ setImgBase64Array ( ( prev ) => {
196+ prev . splice ( index , 1 ) ;
197+ return [ ...prev ] ;
198+ } ) ;
199+ } }
200+ className = "close-icon"
201+ display = { [ '' , 'none' ] }
202+ />
203+ < Image
204+ alt = { 'img' }
205+ src = { src }
206+ w = { '80px' }
207+ h = { '80px' }
208+ rounded = { 'md' }
209+ objectFit = { 'cover' }
210+ />
211+ </ Box >
212+ ) ) }
213+ </ Flex >
96214 { /* input area */ }
97215 < Textarea
98216 ref = { TextareaDom }
99217 py = { 0 }
100218 pr = { [ '45px' , '55px' ] }
219+ pl = { [ '36px' , '40px' ] }
220+ mt = { imgBase64Array . length > 0 ? 4 : 0 }
101221 border = { 'none' }
102222 _focusVisible = { {
103223 border : 'none'
@@ -124,13 +244,28 @@ const MessageInput = ({
124244 onKeyDown = { ( e ) => {
125245 // enter send.(pc or iframe && enter and unPress shift)
126246 if ( ( isPc || window !== parent ) && e . keyCode === 13 && ! e . shiftKey ) {
127- onSendMessage ( TextareaDom . current ?. value || '' ) ;
247+ handleSend ( ) ;
128248 e . preventDefault ( ) ;
129249 }
130250 // 全选内容
131251 // @ts -ignore
132252 e . key === 'a' && e . ctrlKey && e . target ?. select ( ) ;
133253 } }
254+ onPaste = { ( e ) => {
255+ const clipboardData = e . clipboardData ;
256+ if ( clipboardData ) {
257+ const items = clipboardData . items ;
258+ const files : File [ ] = [ ] ;
259+ for ( let i = 0 ; i < items . length ; i ++ ) {
260+ const item = items [ i ] ;
261+ if ( item . kind === 'file' ) {
262+ const file = item . getAsFile ( ) ;
263+ files . push ( file as File ) ;
264+ }
265+ }
266+ setFileList ( files ) ;
267+ }
268+ } }
134269 />
135270 < Flex
136271 position = { 'absolute' }
@@ -195,7 +330,7 @@ const MessageInput = ({
195330 return onStop ( ) ;
196331 }
197332 if ( havInput ) {
198- onSendMessage ( TextareaDom . current ?. value || '' ) ;
333+ return handleSend ( ) ;
199334 }
200335 } }
201336 >
0 commit comments