CoolFace
Apppublic

code2024/bingo

sourceHugging Facemitupdated 3y agoView on Hugging Face
0likes
chat-image.tsx171 linesDownload Raw Back to components
1import {2  useEffect,3  useState,4  useCallback,5  ChangeEvent,6  ClipboardEvent,7  MouseEventHandler,8  FormEvent,9  useRef10} from "react"11import Image from 'next/image'12import PasteIcon from '@/assets/images/paste.svg'13import UploadIcon from '@/assets/images/upload.svg'14import CameraIcon from '@/assets/images/camera.svg'15import { useBing } from '@/lib/hooks/use-bing'16import { cn } from '@/lib/utils'17 18interface ChatImageProps extends Pick<ReturnType<typeof useBing>, 'uploadImage'> {}19 20const preventDefault: MouseEventHandler<HTMLDivElement> = (event) => {21  event.nativeEvent.stopImmediatePropagation()22}23 24const toBase64 = (file: File): Promise<string> => new Promise((resolve, reject) => {25  const reader = new FileReader()26  reader.readAsDataURL(file)27  reader.onload = () => resolve(reader.result as string)28  reader.onerror = reject29})30 31export function ChatImage({ children, uploadImage }: React.PropsWithChildren<ChatImageProps>) {32  const videoRef = useRef<HTMLVideoElement>(null)33  const canvasRef = useRef<HTMLCanvasElement>(null)34  const mediaStream = useRef<MediaStream>()35  const [panel, setPanel] = useState('none')36 37  const upload = useCallback((url: string) => {38    if (url) {39      uploadImage(url)40    }41    setPanel('none')42  }, [panel])43 44  const onUpload = useCallback(async (event: ChangeEvent<HTMLInputElement>) => {45    const file = event.target.files?.[0]46    if (file) {47      const fileDataUrl = await toBase64(file)48      if (fileDataUrl) {49        upload(fileDataUrl)50      }51    }52  }, [])53 54  const onPaste = useCallback((event: ClipboardEvent<HTMLInputElement>) => {55    const pasteUrl = event.clipboardData.getData('text') ?? ''56    upload(pasteUrl)57  }, [])58 59  const onEnter = useCallback((event: FormEvent<HTMLFormElement>) => {60    event.preventDefault()61    event.stopPropagation()62    // @ts-ignore63    const inputUrl = event.target.elements.image.value64    if (inputUrl) {65      upload(inputUrl)66    }67  }, [])68 69  const openVideo: MouseEventHandler<HTMLButtonElement> = async (event) => {70    event.stopPropagation()71    setPanel('camera-mode')72  }73 74  const onCapture = () => {75    if (canvasRef.current && videoRef.current) {76      const canvas = canvasRef.current77      canvas.width = videoRef.current!.videoWidth78      canvas.height = videoRef.current!.videoHeight79      canvas.getContext('2d')?.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height)80      const cameraUrl = canvas.toDataURL('image/jpeg')81      upload(cameraUrl)82    }83  }84 85  useEffect(() => {86    const handleBlur = () => {87      if (panel !== 'none') {88        setPanel('none')89      }90    }91    document.addEventListener('click', handleBlur)92    return () => {93      document.removeEventListener('click', handleBlur)94    }95  }, [panel])96 97  useEffect(() => {98    if (panel === 'camera-mode') {99      navigator.mediaDevices.getUserMedia({ video: true, audio: false })100      .then(videoStream => {101        mediaStream.current = videoStream102        if (videoRef.current) {103          videoRef.current.srcObject = videoStream104        }105      })106    } else {107      if (mediaStream.current) {108        mediaStream.current.getTracks().forEach(function(track) {109          track.stop()110        })111        mediaStream.current = undefined112      }113    }114  }, [panel])115 116  return (117    <div className="visual-search-container">118      <div onClick={() => panel === 'none' ? setPanel('normal') : setPanel('none')}>{children}</div>119      <div className={cn('visual-search', panel)} onClick={preventDefault}>120        <div className="normal-content">121          <div className="header">122            <h4>添加图像</h4>123          </div>124          <div className="paste">125            <Image alt="paste" src={PasteIcon} width={24} />126            <form onSubmitCapture={onEnter}>127              <input128                className="paste-input"129                id="sb_imgpst"130                type="text"131                name="image"132                placeholder="粘贴图像 URL"133                aria-label="粘贴图像 URL"134                onPaste={onPaste}135                onClickCapture={(e) => e.stopPropagation()}136              />137            </form>138          </div>139          <div className="buttons">140            <button type="button" aria-label="从此设备上传">141              <input142                id="vs_fileinput"143                className="fileinput"144                type="file"145                accept="image/gif, image/jpeg, image/png, image/webp"146                onChange={onUpload}147              />148              <Image alt="uplaod" src={UploadIcon} width={20} />149              从此设备上传150            </button>151            <button type="button" aria-label="拍照" onClick={openVideo}>152              <Image alt="camera" src={CameraIcon} width={20} />153              拍照154            </button>155          </div>156        </div>157        {panel === 'camera-mode' && <div className="cam-content">158          <div className="webvideo-container">159            <video className="webvideo" autoPlay muted playsInline ref={videoRef} />160            <canvas className="webcanvas" ref={canvasRef} />161          </div>162          <div className="cambtn" role="button" aria-label="拍照" onClick={onCapture}>163            <div className="cam-btn-circle-large"></div>164            <div className="cam-btn-circle-small"></div>165          </div>166        </div>}167      </div>168    </div>169  )170}171