observer / src /app /observe.tsx
jbilcke-hf's picture
jbilcke-hf HF staff
fixed some issues
1896b82
"use client"
import { useCallback, useEffect, useRef, useState, useTransition } from "react"
import { useInterval } from "usehooks-ts"
import Webcam from "react-webcam"
import AutoSizer from "react-virtualized-auto-sizer"
import { see } from "./engine/see"
import { Progress } from "./interface/progress"
export function Observe({
onObserve,
}: {
onObserve: (observation: string, image: string) => void
}) {
const [_isPending, startTransition] = useTransition()
const [img, setImg] = useState<string>("")
const webcamRef = useRef<Webcam>(null)
const [isInitialized, setInitialized] = useState(false)
const [frameNumber, setFrameNumber] = useState(0)
const [isBusy, setBusy] = useState(false)
const [lastObservation, setLastObservation] = useState("Nothing to see yet.")
const [lastObservedAt, setLastObservedAt] = useState(Date.now())
const defaultWidth = 1280
const defaultHeight = 1024 // 720
// minimum wait time between calls
const minimumWaitTimeInSec = 10
// in case we need to record a video, check the last part of
// https://blog.openreplay.com/capture-real-time-images-and-videos-with-react-webcam/
const capture = useCallback(() => {
if (!webcamRef.current) { return }
const imageSrc = webcamRef.current.getScreenshot()
if (!imageSrc) { return }
setImg(imageSrc)
setFrameNumber(frameNumber + 1)
return imageSrc
}, [webcamRef])
// note: for some strange reason, the webcam (at least on macOS)
// has a "fade in effect", which means in the first few seconds,
// eg. if we capture at 800ms, if will be darker than normal
useEffect(() => {
if (webcamRef.current && img && !isInitialized) {
setInitialized(true)
}
}, [webcamRef.current, img, isInitialized])
const observe = () => {
if (isBusy) {
// console.log("we are already predicting: skippping turn")
return
}
const currentTimeInMs = Date.now()
const elapsedTimeInMs = currentTimeInMs - lastObservedAt
const elapsedTimeInSec = elapsedTimeInMs / 1000
if (elapsedTimeInSec < minimumWaitTimeInSec) {
// console.log("minimum wait time between calls not reached: skipping turn")
return
}
setBusy(true)
// console.log("Capturing new frame from webcam..")
startTransition(async () => {
const imageBase64 = capture()
if (!imageBase64) {
console.log("Failed to capture a new frame")
setTimeout(() => {
setBusy(false)
setLastObservedAt(Date.now())
}, 2000)
return
}
const prompt = `What do you see here?`
console.log("JULIAN: disabled watch")
// console.log("Calling IDEFICS..")
const newObservation = await see({ prompt, imageBase64 })
// console.log("New observation: ", newObservation)
if (newObservation && newObservation !== lastObservation) {
// console.log("update!")
setLastObservation(newObservation || "")
onObserve(newObservation || "", imageBase64)
}
setLastObservedAt(Date.now())
// comment to disable the infinite loop!
setBusy(false)
})
// console.log("observation ended!")
}
useInterval(() => {
observe()
}, 1000)
return (
<AutoSizer>
{({ height, width }) => (
<>
<Webcam
ref={webcamRef}
className="fixed top-0 left-0 right-0 w-screen"
screenshotFormat='image/jpeg'
// screenshotFormat="image/webp"
mirrored={false}
videoConstraints={{
width: { min: defaultWidth },
height: { min: defaultHeight },
aspectRatio: defaultWidth / defaultHeight,
facingMode: "user",
// if the device allows it, we can use the back camera
// facingMode: { exact: "environment" }
} as MediaTrackConstraints}
/>
<Progress
isLoading={isBusy}
resetKey=""
className="right-6"
/>
</>
)}
</AutoSizer>
)
}