diff --git a/www/app/components/dashboard.js b/www/app/components/dashboard.js index becbd4a5..94bae58c 100644 --- a/www/app/components/dashboard.js +++ b/www/app/components/dashboard.js @@ -1,18 +1,16 @@ -import { Mulberry32 } from "../utils.js"; import React, { useState, useEffect } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faChevronRight, faChevronDown, + faLinkSlash, } from "@fortawesome/free-solid-svg-icons"; export function Dashboard({ - isRecording, - onRecord, transcriptionText, finalSummary, topics, - stream, + disconnected, }) { const [openIndex, setOpenIndex] = useState(null); const [autoscrollEnabled, setAutoscrollEnabled] = useState(true); @@ -109,6 +107,15 @@ export function Dashboard({ )} + {disconnected && ( +
+
+ + Disconnected +
+
+ )} + diff --git a/www/app/components/record.js b/www/app/components/record.js index f683f2b7..445f7e50 100644 --- a/www/app/components/record.js +++ b/www/app/components/record.js @@ -2,10 +2,14 @@ import React, { useRef, useEffect, useState } from "react"; import WaveSurfer from "wavesurfer.js"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faDownload } from "@fortawesome/free-solid-svg-icons"; + import Dropdown from "react-dropdown"; import "react-dropdown/style.css"; import CustomRecordPlugin from "./CustomRecordPlugin"; +import { formatTime } from "../utils"; const AudioInputsDropdown = (props) => { const [ddOptions, setDdOptions] = useState([]); @@ -51,6 +55,9 @@ export default function Recorder(props) { const [isRecording, setIsRecording] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [deviceId, setDeviceId] = useState(null); + const [currentTime, setCurrentTime] = useState(0); + const [timeInterval, setTimeInterval] = useState(null); + const [duration, setDuration] = useState(0); useEffect(() => { document.getElementById("play-btn").disabled = true; @@ -76,6 +83,7 @@ export default function Recorder(props) { _wavesurfer.on("pause", () => { setIsPlaying(false); }); + _wavesurfer.on("timeupdate", setCurrentTime); setRecord(_wavesurfer.registerPlugin(CustomRecordPlugin.create())); setWavesurfer(_wavesurfer); @@ -87,6 +95,33 @@ export default function Recorder(props) { } }, []); + useEffect(() => { + if (record) { + return record.on("stopRecording", () => { + const link = document.getElementById("download-recording"); + link.href = record.getRecordedUrl(); + link.download = "reflector-recording.webm"; + link.style.visibility = "visible"; + }); + } + }, [record]); + + useEffect(() => { + if (isRecording) { + const interval = setInterval(() => { + setCurrentTime((prev) => prev + 1); + }, 1000); + setTimeInterval(interval); + return () => clearInterval(interval); + } else { + clearInterval(timeInterval); + setCurrentTime((prev) => { + setDuration(prev); + return 0; + }); + } + }, [isRecording]); + const handleRecClick = async () => { if (!record) return console.log("no record"); @@ -113,8 +148,15 @@ export default function Recorder(props) { wavesurfer?.playPause(); }; + const timeLabel = () => { + if (isRecording) return formatTime(currentTime); + else if (duration) + return `${formatTime(currentTime)}/${formatTime(duration)}`; + else ""; + }; + return ( -
+
  @@ -135,10 +177,21 @@ export default function Recorder(props) { > {isPlaying ? "Pause" : "Play"} + + +
- {/* TODO: Download audio tag */} - {/* TODO: current time / audio duration */} +
+ {isRecording && ( +
+ )} + {timeLabel()} +
); } diff --git a/www/app/page.js b/www/app/page.js index f08dac8b..3ebf8e32 100644 --- a/www/app/page.js +++ b/www/app/page.js @@ -1,5 +1,5 @@ "use client"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import Recorder from "./components/record.js"; import { Dashboard } from "./components/dashboard.js"; import useWebRTC from "./components/webrtc.js"; @@ -9,6 +9,17 @@ import "../public/button.css"; const App = () => { const [stream, setStream] = useState(null); + const [disconnected, setDisconnected] = useState(false); + + useEffect(() => { + if (process.env.NEXT_PUBLIC_ENV === "development") { + document.onkeyup = (e) => { + if (e.key === "d") { + setDisconnected((prev) => !prev); + } + }; + } + }, []); const transcript = useTranscript(); const webRTC = useWebRTC(stream, transcript.response?.id); @@ -33,6 +44,7 @@ const App = () => { finalSummary={webSockets.finalSummary} topics={webSockets.topics} stream={stream} + disconnected={disconnected} />
); diff --git a/www/app/utils.js b/www/app/utils.js index 37c4dee7..79e8ceae 100644 --- a/www/app/utils.js +++ b/www/app/utils.js @@ -17,3 +17,15 @@ export function Mulberry32(seed) { return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } + +export const formatTime = (seconds) => { + let hours = Math.floor(seconds / 3600); + let minutes = Math.floor((seconds % 3600) / 60); + let secs = Math.floor(seconds % 60); + + let timeString = `${hours > 0 ? hours + ":" : ""}${minutes + .toString() + .padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; + + return timeString; +};