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}
className="flex-grow w-full"
disabled={props.disabled}
/>
</div>
);
}
return null;
};
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,23 +52,17 @@ 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} />
@@ -90,19 +71,37 @@ const TranscriptCreate = () => {
</div>
{disconnected && <DisconnectedIndicator />}
</div>
</>
) : (
<>
<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>
<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="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 mt-5">
<p className="text-gray-500 text-center">
Checking permission...
</p>
) : (
<>
<p className="text-gray-500 text-center mt-5">
<p className="text-gray-500 text-center">
Reflector needs access to your microphone to work.
<br />
{permissionDenied
@@ -119,6 +118,8 @@ const TranscriptCreate = () => {
</>
)}
</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,6 +275,7 @@ export default function Recorder(props: RecorderProps) {
</>
)}
{!hasRecorded && (
<>
<button
className={`${
isRecording
@@ -270,6 +287,29 @@ export default function Recorder(props: RecorderProps) {
>
{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>
);