From eca5330f4480f90d1a2137e77a990217c47d8b5b Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 13 Oct 2023 19:10:15 +0200 Subject: [PATCH] adds meeting setup --- www/app/transcripts/[transcriptId]/page.tsx | 2 +- .../[transcriptId]/record/page.tsx | 146 +++++++++++ www/app/transcripts/createTranscript.ts | 54 ++++ www/app/transcripts/new/page.tsx | 237 ++++++------------ www/app/transcripts/useTranscript.ts | 50 +--- 5 files changed, 281 insertions(+), 208 deletions(-) create mode 100644 www/app/transcripts/[transcriptId]/record/page.tsx create mode 100644 www/app/transcripts/createTranscript.ts diff --git a/www/app/transcripts/[transcriptId]/page.tsx b/www/app/transcripts/[transcriptId]/page.tsx index 74fede74..6cf3b531 100644 --- a/www/app/transcripts/[transcriptId]/page.tsx +++ b/www/app/transcripts/[transcriptId]/page.tsx @@ -21,7 +21,7 @@ type TranscriptDetails = { export default function TranscriptDetails(details: TranscriptDetails) { const api = getApi(); - const transcript = useTranscript(null, api, details.params.transcriptId); + const transcript = useTranscript(details.params.transcriptId); const topics = useTopics(api, details.params.transcriptId); const waveform = useWaveform(api, details.params.transcriptId); const useActiveTopic = useState(null); diff --git a/www/app/transcripts/[transcriptId]/record/page.tsx b/www/app/transcripts/[transcriptId]/record/page.tsx new file mode 100644 index 00000000..6e17e07f --- /dev/null +++ b/www/app/transcripts/[transcriptId]/record/page.tsx @@ -0,0 +1,146 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import Recorder from "../../recorder"; +import { TopicList } from "../../topicList"; +import useWebRTC from "../../useWebRTC"; +import useTranscript from "../../useTranscript"; +import { useWebSockets } from "../../useWebSockets"; +import useAudioDevice from "../../useAudioDevice"; +import "../../../styles/button.css"; +import { Topic } from "../../webSocketTypes"; +import getApi from "../../../lib/getApi"; +import LiveTrancription from "../../liveTranscription"; +import DisconnectedIndicator from "../../disconnectedIndicator"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faGear } from "@fortawesome/free-solid-svg-icons"; +import { lockWakeState, releaseWakeState } from "../../../lib/wakeLock"; + +type TranscriptDetails = { + params: { + transcriptId: string; + }; +}; + +const TranscriptRecord = (details: TranscriptDetails) => { + const [stream, setStream] = useState(null); + const [disconnected, setDisconnected] = useState(false); + const useActiveTopic = useState(null); + + useEffect(() => { + if (process.env.NEXT_PUBLIC_ENV === "development") { + document.onkeyup = (e) => { + if (e.key === "d") { + setDisconnected((prev) => !prev); + } + }; + } + }, []); + + const transcript = useTranscript(details.params.transcriptId); + const api = getApi(); + const webRTC = useWebRTC(stream, details.params.transcriptId, api); + const webSockets = useWebSockets(details.params.transcriptId); + + const { + loading, + permissionOk, + permissionDenied, + audioDevices, + requestPermission, + getAudioStream, + } = useAudioDevice(); + + const [hasRecorded, setHasRecorded] = useState(false); + const [transcriptStarted, setTranscriptStarted] = useState(false); + + useEffect(() => { + if (!transcriptStarted && webSockets.transcriptText.length !== 0) + setTranscriptStarted(true); + }, [webSockets.transcriptText]); + + useEffect(() => { + if (transcript?.response?.longSummary) { + const newUrl = `/transcripts/${transcript.response.id}`; + // Shallow redirection does not work on NextJS 13 + // https://github.com/vercel/next.js/discussions/48110 + // https://github.com/vercel/next.js/discussions/49540 + // router.push(newUrl, undefined, { shallow: true }); + history.replaceState({}, "", newUrl); + } + }); + + useEffect(() => { + lockWakeState(); + return () => { + releaseWakeState(); + }; + }, []); + + return ( + <> + { + setStream(null); + setHasRecorded(true); + webRTC?.send(JSON.stringify({ cmd: "STOP" })); + }} + topics={webSockets.topics} + getAudioStream={getAudioStream} + useActiveTopic={useActiveTopic} + isPastMeeting={false} + audioDevices={audioDevices} + /> + +
+ + +
+ {!hasRecorded ? ( + <> + {transcriptStarted && ( +

Transcription

+ )} +
+
+ {!transcriptStarted ? ( +
+ The conversation transcript will appear here shortly after + you start recording. +
+ ) : ( + + )} +
+
+ + ) : ( +
+
+ +
+

+ We are generating the final summary for you. This may take a + couple of minutes. Please do not navigate away from the page + during this time. +

+
+ )} +
+
+ + {disconnected && } + + ); +}; + +export default TranscriptRecord; diff --git a/www/app/transcripts/createTranscript.ts b/www/app/transcripts/createTranscript.ts new file mode 100644 index 00000000..32024cb4 --- /dev/null +++ b/www/app/transcripts/createTranscript.ts @@ -0,0 +1,54 @@ +import { useEffect, useState } from "react"; +import { DefaultApi, V1TranscriptsCreateRequest } from "../api/apis/DefaultApi"; +import { GetTranscript } from "../api"; +import { useError } from "../(errors)/errorContext"; +import getApi from "../lib/getApi"; + +type CreateTranscript = { + response: GetTranscript | null; + loading: boolean; + error: Error | null; + create: (params: V1TranscriptsCreateRequest["createTranscript"]) => void; +}; + +const useCreateTranscript = (): CreateTranscript => { + const [response, setResponse] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setErrorState] = useState(null); + const { setError } = useError(); + const api = getApi(); + + const create = (params: V1TranscriptsCreateRequest["createTranscript"]) => { + if (loading) return; + + setLoading(true); + const requestParameters: V1TranscriptsCreateRequest = { + createTranscript: { + name: params.name || "Weekly All-Hands", // Default + targetLanguage: params.targetLanguage || "en", // Default + }, + }; + + console.debug( + "POST - /v1/transcripts/ - Requesting new transcription creation", + requestParameters, + ); + + api + .v1TranscriptsCreate(requestParameters) + .then((result) => { + setResponse(result); + setLoading(false); + console.debug("New transcript created:", result); + }) + .catch((err) => { + setError(err); + setErrorState(err); + setLoading(false); + }); + }; + + return { response, loading, error, create }; +}; + +export default useCreateTranscript; diff --git a/www/app/transcripts/new/page.tsx b/www/app/transcripts/new/page.tsx index b4e294d6..365770b3 100644 --- a/www/app/transcripts/new/page.tsx +++ b/www/app/transcripts/new/page.tsx @@ -1,7 +1,5 @@ "use client"; import React, { useEffect, useState } from "react"; -import Recorder from "../recorder"; -import { TopicList } from "../topicList"; import useWebRTC from "../useWebRTC"; import useTranscript from "../useTranscript"; import { useWebSockets } from "../useWebSockets"; @@ -9,35 +7,37 @@ import useAudioDevice from "../useAudioDevice"; import "../../styles/button.css"; import { Topic } from "../webSocketTypes"; import getApi from "../../lib/getApi"; -import LiveTrancription from "../liveTranscription"; -import DisconnectedIndicator from "../disconnectedIndicator"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faGear } from "@fortawesome/free-solid-svg-icons"; import About from "../../(aboutAndPrivacy)/about"; import Privacy from "../../(aboutAndPrivacy)/privacy"; import { lockWakeState, releaseWakeState } from "../../lib/wakeLock"; import { useRouter } from "next/navigation"; +import createTranscript from "../createTranscript"; +import { GetTranscript } from "../../api"; +import { Router } from "next/router"; +import useCreateTranscript from "../createTranscript"; const TranscriptCreate = () => { - const [stream, setStream] = useState(null); - const [disconnected, setDisconnected] = useState(false); - const useActiveTopic = useState(null); - - useEffect(() => { - if (process.env.NEXT_PUBLIC_ENV === "development") { - document.onkeyup = (e) => { - if (e.key === "d") { - setDisconnected((prev) => !prev); - } - }; - } - }, []); - - const api = getApi(); - const transcript = useTranscript(stream, api); - const webRTC = useWebRTC(stream, transcript?.response?.id, api); - const webSockets = useWebSockets(transcript?.response?.id); + // const transcript = useTranscript(stream, api); const router = useRouter(); + const api = getApi(); + + const [name, setName] = useState(); + const nameChange = (event: React.ChangeEvent) => { + setName(event.target.value); + }; + const [targetLanguage, setTargetLanguage] = useState(); + + const createTranscript = useCreateTranscript(); + + const send = () => { + if (createTranscript.loading || permissionDenied) return; + createTranscript.create({ name, targetLanguage }); + }; + useEffect(() => { + createTranscript.response && + router.push(`/transcripts/${createTranscript.response.id}/record`); + }, [createTranscript.response]); + const { loading, permissionOk, @@ -47,151 +47,62 @@ const TranscriptCreate = () => { getAudioStream, } = useAudioDevice(); - const [hasRecorded, setHasRecorded] = useState(false); - const [transcriptStarted, setTranscriptStarted] = useState(false); - - useEffect(() => { - if (!transcriptStarted && webSockets.transcriptText.length !== 0) - setTranscriptStarted(true); - }, [webSockets.transcriptText]); - - useEffect(() => { - if (transcript?.response?.id) { - const newUrl = `/transcripts/${transcript.response.id}`; - // Shallow redirection does not work on NextJS 13 - // https://github.com/vercel/next.js/discussions/48110 - // https://github.com/vercel/next.js/discussions/49540 - // router.push(newUrl, undefined, { shallow: true }); - history.replaceState({}, "", newUrl); - } - }); - - useEffect(() => { - lockWakeState(); - return () => { - releaseWakeState(); - }; - }, []); - return ( <> - {permissionOk ? ( - <> - { - webRTC?.send(JSON.stringify({ cmd: "STOP" })); - setStream(null); - setHasRecorded(true); - }} - topics={webSockets.topics} - getAudioStream={getAudioStream} - useActiveTopic={useActiveTopic} - isPastMeeting={false} - audioDevices={audioDevices} - /> - -
- - -
- {!hasRecorded ? ( - <> - {transcriptStarted && ( -

Transcription

- )} -
-
- {!transcriptStarted ? ( -
- The conversation transcript will appear here shortly - after you start recording. -
- ) : ( - - )} -
-
- +
+
+
+
+
+

+ Welcome to reflector.media +

+

+ Reflector is a transcription and summarization pipeline that + transforms audio into knowledge. The output is meeting minutes + and topic summaries enabling topic-specific analyses stored in + your systems of record. This is accomplished on your + infrastructure – without 3rd parties – keeping your data + private, secure, and organized. +

+ + + +

+ Audio Permissions +

+ {loading ? ( +

Checking permission...

+ ) : permissionOk ? ( + <> Microphone permission granted ) : ( -
-
- -
-

- We are generating the final summary for you. This may take a - couple of minutes. Please do not navigate away from the page - during this time. + <> +

+ In order to use Reflector, we kindly request permission to + access your microphone during meetings and events. +
+ +
+ {permissionDenied + ? "Permission to use your microphone was denied, please change the permission setting in your browser and refresh this page." + : "Please grant permission to continue."}

-
+ + )} -
+
+
- - {disconnected && } - - ) : ( - <> -
-
-
-
-
-

- Welcome to reflector.media -

-

- Reflector is a transcription and summarization pipeline that - transforms audio into knowledge. The output is meeting - minutes and topic summaries enabling topic-specific analyses - stored in your systems of record. This is accomplished on - your infrastructure – without 3rd parties – keeping your - data private, secure, and organized. -

- -

- Audio Permissions -

- {loading ? ( -

Checking permission...

- ) : ( - <> -

- In order to use Reflector, we kindly request permission - to access your microphone during meetings and events. -
- -
- {permissionDenied - ? "Permission to use your microphone was denied, please change the permission setting in your browser and refresh this page." - : "Please grant permission to continue."} -

- - - )} -
-
-
-
- - )} + + ); }; diff --git a/www/app/transcripts/useTranscript.ts b/www/app/transcripts/useTranscript.ts index 7dbc969a..eb39b202 100644 --- a/www/app/transcripts/useTranscript.ts +++ b/www/app/transcripts/useTranscript.ts @@ -1,11 +1,11 @@ import { useEffect, useState } from "react"; import { - DefaultApi, V1TranscriptGetRequest, V1TranscriptsCreateRequest, } from "../api/apis/DefaultApi"; import { GetTranscript } from "../api"; import { useError } from "../(errors)/errorContext"; +import getApi from "../lib/getApi"; type Transcript = { response: GetTranscript | null; @@ -13,23 +13,12 @@ type Transcript = { error: Error | null; }; -const useTranscript = ( - stream: MediaStream | null, - api: DefaultApi, - id: string | null = null, -): Transcript => { +const useTranscript = (id: string | null): Transcript => { const [response, setResponse] = useState(null); const [loading, setLoading] = useState(false); const [error, setErrorState] = useState(null); const { setError } = useError(); - - const getOrCreateTranscript = (id: string | null) => { - if (id) { - getTranscript(id); - } else if (stream) { - createTranscript(); - } - }; + const api = getApi(); const getTranscript = (id: string | null) => { if (!id) throw new Error("Transcript ID is required to get transcript"); @@ -43,34 +32,7 @@ const useTranscript = ( .then((result) => { setResponse(result); setLoading(false); - console.debug("New transcript created:", result); - }) - .catch((err) => { - setError(err); - setErrorState(err); - }); - }; - - const createTranscript = () => { - setLoading(true); - const requestParameters: V1TranscriptsCreateRequest = { - createTranscript: { - name: "Weekly All-Hands", // Hardcoded for now - targetLanguage: "en", // Hardcoded for now - }, - }; - - console.debug( - "POST - /v1/transcripts/ - Requesting new transcription creation", - requestParameters, - ); - - api - .v1TranscriptsCreate(requestParameters) - .then((result) => { - setResponse(result); - setLoading(false); - console.debug("New transcript created:", result); + console.debug("Transcript Loaded:", result); }) .catch((err) => { setError(err); @@ -79,8 +41,8 @@ const useTranscript = ( }; useEffect(() => { - getOrCreateTranscript(id); - }, [id, stream]); + getTranscript(id); + }, [id]); return { response, loading, error }; };