import { useEffect, useState, useCallback, ChangeEvent, ClipboardEvent, MouseEventHandler, FormEvent, useRef } from "react" import Image from 'next/image' import PasteIcon from '@/assets/images/paste.svg' import UploadIcon from '@/assets/images/upload.svg' import CameraIcon from '@/assets/images/camera.svg' import { useBing } from '@/lib/hooks/use-bing' import { cn } from '@/lib/utils' interface ChatImageProps extends Pick<ReturnType<typeof useBing>, 'uploadImage'> {} const preventDefault: MouseEventHandler<HTMLDivElement> = (event) => { event.nativeEvent.stopImmediatePropagation() } const toBase64 = (file: File): Promise<string> => new Promise((resolve, reject) => { const reader = new FileReader() reader.readAsDataURL(file) reader.onload = () => resolve(reader.result as string) reader.onerror = reject }) export function ChatImage({ children, uploadImage }: React.PropsWithChildren<ChatImageProps>) { const videoRef = useRef<HTMLVideoElement>(null) const canvasRef = useRef<HTMLCanvasElement>(null) const mediaStream = useRef<MediaStream>() const [panel, setPanel] = useState('none') const upload = useCallback((url: string) => { if (url) { uploadImage(url) } setPanel('none') }, [panel]) const onUpload = useCallback(async (event: ChangeEvent<HTMLInputElement>) => { const file = event.target.files?.[0] if (file) { const fileDataUrl = await toBase64(file) if (fileDataUrl) { upload(fileDataUrl) } } }, []) const onPaste = useCallback((event: ClipboardEvent<HTMLInputElement>) => { const pasteUrl = event.clipboardData.getData('text') ?? '' upload(pasteUrl) }, []) const onEnter = useCallback((event: FormEvent<HTMLFormElement>) => { event.preventDefault() event.stopPropagation() // @ts-ignore const inputUrl = event.target.elements.image.value if (inputUrl) { upload(inputUrl) } }, []) const openVideo: MouseEventHandler<HTMLButtonElement> = async (event) => { event.stopPropagation() setPanel('camera-mode') } const onCapture = () => { if (canvasRef.current && videoRef.current) { const canvas = canvasRef.current canvas.width = videoRef.current!.videoWidth canvas.height = videoRef.current!.videoHeight canvas.getContext('2d')?.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height) const cameraUrl = canvas.toDataURL('image/jpeg') upload(cameraUrl) } } useEffect(() => { const handleBlur = () => { if (panel !== 'none') { setPanel('none') } } document.addEventListener('click', handleBlur) return () => { document.removeEventListener('click', handleBlur) } }, [panel]) useEffect(() => { if (panel === 'camera-mode') { navigator.mediaDevices.getUserMedia({ video: true, audio: false }) .then(videoStream => { mediaStream.current = videoStream if (videoRef.current) { videoRef.current.srcObject = videoStream } }) } else { if (mediaStream.current) { mediaStream.current.getTracks().forEach(function(track) { track.stop() }) mediaStream.current = undefined } } }, [panel]) return ( <div className="visual-search-container"> <div onClick={() => panel === 'none' ? setPanel('normal') : setPanel('none')}>{children}</div> <div className={cn('visual-search', panel)} onClick={preventDefault}> <div className="normal-content"> <div className="header"> <h4>添加图像</h4> </div> <div className="paste"> <Image alt="paste" src={PasteIcon} width={24} /> <form onSubmitCapture={onEnter}> <input className="paste-input" id="sb_imgpst" type="text" name="image" placeholder="粘贴图像 URL" aria-label="粘贴图像 URL" onPaste={onPaste} onClickCapture={(e) => e.stopPropagation()} /> </form> </div> <div className="buttons"> <button type="button" aria-label="从此设备上传"> <input id="vs_fileinput" className="fileinput" type="file" accept="image/gif, image/jpeg, image/png, image/webp" onChange={onUpload} /> <Image alt="uplaod" src={UploadIcon} width={20} /> 从此设备上传 </button> <button type="button" aria-label="拍照" onClick={openVideo}> <Image alt="camera" src={CameraIcon} width={20} /> 拍照 </button> </div> </div> {panel === 'camera-mode' && <div className="cam-content"> <div className="webvideo-container"> <video className="webvideo" autoPlay muted playsInline ref={videoRef} /> <canvas className="webcanvas" ref={canvasRef} /> </div> <div className="cambtn" role="button" aria-label="拍照" onClick={onCapture}> <div className="cam-btn-circle-large"></div> <div className="cam-btn-circle-small"></div> </div> </div>} </div> </div> ) }