bingo-silver / src /components /chat-image.tsx
SilverStarShadow's picture
Duplicate from hf4all/bingo
15bfa8d
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>
)
}