microphone switch and design improvements

This commit is contained in:
Sara
2023-09-20 17:08:47 +02:00
parent fe510238c0
commit 2576a6e4e2
4 changed files with 134 additions and 97 deletions

View File

@@ -23,7 +23,7 @@ const ErrorMessage: React.FC = () => {
setIsVisible(false);
setError(null);
}}
className="max-w-xs z-50 fixed top-16 right-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded transition-opacity duration-300 ease-out opacity-100 hover:opacity-75 cursor-pointer transform hover:scale-105"
className="max-w-xs z-50 fixed bottom-5 right-5 md:bottom-10 md:right-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded transition-opacity duration-300 ease-out opacity-100 hover:opacity-80 cursor-pointer transform hover:scale-105"
role="alert"
>
<span className="block sm:inline">{error?.message}</span>

View File

@@ -1,5 +1,3 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMicrophone } from "@fortawesome/free-solid-svg-icons";
import React, { useEffect, useState } from "react";
import Dropdown, { Option } from "react-dropdown";
import "react-dropdown/style.css";
@@ -7,36 +5,34 @@ import "react-dropdown/style.css";
const AudioInputsDropdown: React.FC<{
audioDevices: Option[];
disabled: boolean;
hide: () => void;
setDeviceId: React.Dispatch<React.SetStateAction<string | null>>;
}> = ({ audioDevices, disabled, setDeviceId }) => {
}> = (props) => {
const [ddOptions, setDdOptions] = useState<Option[]>([]);
useEffect(() => {
if (audioDevices) {
setDdOptions(audioDevices);
setDeviceId(audioDevices.length > 0 ? audioDevices[0].value : null);
if (props.audioDevices) {
setDdOptions(props.audioDevices);
props.setDeviceId(
props.audioDevices.length > 0 ? props.audioDevices[0].value : null,
);
}
}, [audioDevices]);
}, [props.audioDevices]);
const handleDropdownChange = (option: Option) => {
setDeviceId(option.value);
props.setDeviceId(option.value);
props.hide();
};
if (audioDevices?.length > 0) {
return (
<div className="flex w-full items-center">
<FontAwesomeIcon icon={faMicrophone} className="p-2" />
<Dropdown
options={ddOptions}
onChange={handleDropdownChange}
value={ddOptions[0]}
className="flex-grow"
disabled={disabled}
/>
</div>
);
}
return null;
return (
<Dropdown
options={ddOptions}
onChange={handleDropdownChange}
value={ddOptions[0]}
className="flex-grow w-full"
disabled={props.disabled}
/>
);
};
export default AudioInputsDropdown;

View File

@@ -17,8 +17,6 @@ const TranscriptCreate = () => {
const [stream, setStream] = useState<MediaStream | null>(null);
const [disconnected, setDisconnected] = useState<boolean>(false);
const useActiveTopic = useState<Topic | null>(null);
const [deviceId, setDeviceId] = useState<string | null>(null);
const [recordStarted, setRecordStarted] = useState(false);
useEffect(() => {
if (process.env.NEXT_PUBLIC_ENV === "development") {
@@ -43,17 +41,6 @@ const TranscriptCreate = () => {
getAudioStream,
} = useAudioDevice();
const getCurrentStream = async () => {
setRecordStarted(true);
return deviceId ? await getAudioStream(deviceId) : null;
};
useEffect(() => {
if (audioDevices.length > 0) {
setDeviceId[audioDevices[0].value];
}
}, [audioDevices]);
return (
<>
{permissionOk ? (
@@ -65,60 +52,74 @@ const TranscriptCreate = () => {
setStream(null);
}}
topics={webSockets.topics}
getAudioStream={getCurrentStream}
getAudioStream={getAudioStream}
useActiveTopic={useActiveTopic}
isPastMeeting={false}
audioDevices={audioDevices}
/>
<div className="grid grid-cols-1 lg:grid-cols-2 grid-rows-2 lg:grid-rows-1 gap-2 lg:gap-4 h-full">
<TopicList
topics={webSockets.topics}
useActiveTopic={useActiveTopic}
/>
<div className="h-full flex flex-col">
<section className="mb-2">
<AudioInputsDropdown
setDeviceId={setDeviceId}
audioDevices={audioDevices}
disabled={recordStarted}
/>
</section>
<section className="w-full h-full bg-blue-400/20 rounded-lg md:rounded-xl px-2 md:px-4 flex flex-col justify-center align-center">
<div className="py-2 h-auto">
<LiveTrancription text={webSockets.transcriptText} />
</div>
</section>
</div>
{disconnected && <DisconnectedIndicator />}
<section className="w-full h-full bg-blue-400/20 rounded-lg md:rounded-xl px-2 md:px-4 flex flex-col justify-center align-center">
<div className="py-2 h-auto">
<LiveTrancription text={webSockets.transcriptText} />
</div>
</section>
</div>
{disconnected && <DisconnectedIndicator />}
</>
) : (
<>
<div className="flex flex-col w-full items-center justify-center px-6 py-8 mt-8 rounded-xl">
<h1 className="text-2xl font-bold">Audio Permissions</h1>
{loading ? (
<p className="text-gray-500 text-center mt-5">
Checking permission...
<div></div>
<section className="flex flex-col w-full h-full items-center justify-evenly p-4 md:px-6 md:py-8">
<div className="flex flex-col max-w-2xl items-center justify-center">
<h1 className="text-2xl font-bold mb-2">Reflector</h1>
<p className="self-start">
Meet Monadical's own Reflector, your audio ally for hassle-free
insights.
</p>
) : (
<>
<p className="text-gray-500 text-center mt-5">
Reflector needs access to your microphone to work.
<br />
{permissionDenied
? "Please reset microphone permissions to continue."
: "Please grant permission to continue."}
</p>
<button
className="mt-4 bg-blue-400 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded m-auto"
onClick={requestPermission}
disabled={permissionDenied}
>
{permissionDenied ? "Access denied" : "Grant Permission"}
</button>
</>
)}
</div>
<p className="mb-4 md:text-justify">
With real-time transcriptions, translations, and summaries,
Reflector captures and categorizes the details of your meetings
and events, all while keeping your data locked down tight on
your own infrastructure. Forget the scribbled notes, endless
recordings, or third-party apps. Discover Reflector, a powerful
new way to elevate knowledge management and accessibility for
all.
</p>
</div>
<div>
<div className="flex flex-col max-w-2xl items-center justify-center">
<h2 className="text-2xl font-bold mb-2">Audio Permissions</h2>
{loading ? (
<p className="text-gray-500 text-center">
Checking permission...
</p>
) : (
<>
<p className="text-gray-500 text-center">
Reflector needs access to your microphone to work.
<br />
{permissionDenied
? "Please reset microphone permissions to continue."
: "Please grant permission to continue."}
</p>
<button
className="mt-4 bg-blue-400 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded m-auto"
onClick={requestPermission}
disabled={permissionDenied}
>
{permissionDenied ? "Access denied" : "Grant Permission"}
</button>
</>
)}
</div>
</div>
</section>
</>
)}
</>

View File

@@ -5,17 +5,21 @@ import RecordPlugin from "../lib/custom-plugins/record";
import CustomRegionsPlugin from "../lib/custom-plugins/regions";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMicrophone } from "@fortawesome/free-solid-svg-icons";
import { faDownload } from "@fortawesome/free-solid-svg-icons";
import { formatTime } from "../lib/time";
import { Topic } from "./webSocketTypes";
import { AudioWaveform } from "../api";
import AudioInputsDropdown from "./audioInputsDropdown";
import { Option } from "react-dropdown";
type RecorderProps = {
setStream?: React.Dispatch<React.SetStateAction<MediaStream | null>>;
onStop?: () => void;
topics: Topic[];
getAudioStream?: () => Promise<MediaStream | null>;
getAudioStream?: (deviceId) => Promise<MediaStream | null>;
audioDevices?: Option[];
useActiveTopic: [
Topic | null,
React.Dispatch<React.SetStateAction<Topic | null>>,
@@ -38,10 +42,11 @@ export default function Recorder(props: RecorderProps) {
const [waveRegions, setWaveRegions] = useState<CustomRegionsPlugin | null>(
null,
);
const [deviceId, setDeviceId] = useState<string | null>(null);
const [recordStarted, setRecordStarted] = useState(false);
const [activeTopic, setActiveTopic] = props.useActiveTopic;
const topicsRef = useRef(props.topics);
const [showDevices, setShowDevices] = useState(false);
useEffect(() => {
if (waveformRef.current) {
@@ -186,8 +191,8 @@ export default function Recorder(props: RecorderProps) {
record.stopRecording();
setIsRecording(false);
setHasRecorded(true);
} else if (props.getAudioStream) {
const stream = await props.getAudioStream();
} else {
const stream = await getCurrentStream();
if (props.setStream) props.setStream(stream);
waveRegions?.clearRegions();
@@ -195,8 +200,6 @@ export default function Recorder(props: RecorderProps) {
await record.startRecording(stream);
setIsRecording(true);
}
} else {
throw new Error("No getAudioStream function provided");
}
};
@@ -210,8 +213,21 @@ export default function Recorder(props: RecorderProps) {
return "";
};
const getCurrentStream = async () => {
setRecordStarted(true);
return deviceId && props.getAudioStream
? await props.getAudioStream(deviceId)
: null;
};
useEffect(() => {
if (props.audioDevices && props.audioDevices.length > 0) {
setDeviceId[props.audioDevices[0].value];
}
}, [props.audioDevices]);
return (
<div className="flex items-center w-full">
<div className="flex items-center w-full relative">
<div className="flex-grow items-end relative">
<div ref={waveformRef} className="flex-grow rounded-2xl h-20"></div>
<div className="absolute right-2 bottom-0">
@@ -259,17 +275,41 @@ export default function Recorder(props: RecorderProps) {
</>
)}
{!hasRecorded && (
<button
className={`${
isRecording
? "bg-red-400 hover:bg-red-500"
: "bg-blue-400 hover:bg-blue-500"
} text-white ml-2 md:ml:4 md:h-[78px] md:min-w-[100px] text-lg`}
onClick={handleRecClick}
disabled={isPlaying}
>
{isRecording ? "Stop" : "Record"}
</button>
<>
<button
className={`${
isRecording
? "bg-red-400 hover:bg-red-500"
: "bg-blue-400 hover:bg-blue-500"
} text-white ml-2 md:ml:4 md:h-[78px] md:min-w-[100px] text-lg`}
onClick={handleRecClick}
disabled={isPlaying}
>
{isRecording ? "Stop" : "Record"}
</button>
{props.audioDevices && props.audioDevices?.length > 0 && (
<>
<button
className="text-center cursor-pointer text-blue-400 hover:text-blue-700 ml-2 md:ml:4 p-2"
onClick={() => setShowDevices((prev) => !prev)}
>
<FontAwesomeIcon icon={faMicrophone} className="h-5 w-auto" />
</button>
<div
className={`absolute z-20 bottom-[-1rem] right-0 bg-white rounded ${
showDevices ? "visible" : "invisible"
}`}
>
<AudioInputsDropdown
setDeviceId={setDeviceId}
audioDevices={props.audioDevices}
disabled={recordStarted}
hide={() => setShowDevices(false)}
/>
</div>
</>
)}
</>
)}
</div>
);