diff --git a/www/app/[domain]/browse/page.tsx b/www/app/[domain]/browse/page.tsx index 21f28c81..b2359b9e 100644 --- a/www/app/[domain]/browse/page.tsx +++ b/www/app/[domain]/browse/page.tsx @@ -1,43 +1,17 @@ "use client"; -import React, { useState, useEffect } from "react"; -import getApi from "../../lib/getApi"; -import { - PageGetTranscript, - GetTranscript, - GetTranscriptFromJSON, -} from "../../api"; +import React, { useState } from "react"; + +import { GetTranscript } from "../../api"; import { Title } from "../../lib/textComponents"; import Pagination from "./pagination"; import Link from "next/link"; -import { useFiefIsAuthenticated } from "@fief/fief/nextjs/react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faGear } from "@fortawesome/free-solid-svg-icons"; +import useTranscriptList from "../transcripts/useTranscriptList"; export default function TranscriptBrowser() { - const api = getApi(); - const [results, setResults] = useState(null); const [page, setPage] = useState(1); - const [isLoading, setIsLoading] = useState(false); - const isAuthenticated = useFiefIsAuthenticated(); - - useEffect(() => { - if (!isAuthenticated) return; - setIsLoading(true); - api - .v1TranscriptsList({ page }) - .then((response) => { - // issue with API layer, conversion for items is not happening - response.items = response.items.map((item) => - GetTranscriptFromJSON(item), - ); - setResults(response); - setIsLoading(false); - }) - .catch(() => { - setResults(null); - setIsLoading(false); - }); - }, [page, isAuthenticated]); + const { loading, response } = useTranscriptList(page); return (
@@ -52,12 +26,12 @@ export default function TranscriptBrowser() {
- {isLoading && ( + {loading && (
)} - {!isLoading && !results ? ( + {!loading && !response && (
No transcripts found, but you can  @@ -73,12 +47,10 @@ export default function TranscriptBrowser() {  to get started.
- ) : ( - <> )}
- {results?.items.map((item: GetTranscript) => ( + {response?.items.map((item: GetTranscript) => (
(""); - const transcript = useTranscript(api, transcriptId); - const topics = useTopics(api, transcriptId); - const waveform = useWaveform(api, transcriptId); - const useActiveTopic = useState(null); - const requireLogin = featureEnabled("requireLogin"); +const protectedPath = true; - useEffect(() => { - if (requireLogin && !isAuthenticated) return; - setTranscriptId(details.params.transcriptId); - }, [api, details.params.transcriptId, isAuthenticated]); +export default function TranscriptDetails(details: TranscriptDetails) { + const transcriptId = details.params.transcriptId; + + const transcript = useTranscript(protectedPath, transcriptId); + const topics = useTopics(protectedPath, transcriptId); + const waveform = useWaveform(protectedPath, transcriptId); + const useActiveTopic = useState(null); + const mp3 = useMp3(protectedPath, transcriptId); if (transcript?.error /** || topics?.error || waveform?.error **/) { return ( @@ -70,6 +64,7 @@ export default function TranscriptDetails(details: TranscriptDetails) { waveform={waveform?.waveform} isPastMeeting={true} transcriptId={transcript?.response?.id} + mp3Blob={mp3.blob} /> )}
diff --git a/www/app/[domain]/transcripts/[transcriptId]/record/page.tsx b/www/app/[domain]/transcripts/[transcriptId]/record/page.tsx index 8e31327c..41a2d053 100644 --- a/www/app/[domain]/transcripts/[transcriptId]/record/page.tsx +++ b/www/app/[domain]/transcripts/[transcriptId]/record/page.tsx @@ -36,9 +36,8 @@ const TranscriptRecord = (details: TranscriptDetails) => { } }, []); - const api = getApi(); - const transcript = useTranscript(api, details.params.transcriptId); - const webRTC = useWebRTC(stream, details.params.transcriptId, api); + const transcript = useTranscript(true, details.params.transcriptId); + const webRTC = useWebRTC(stream, details.params.transcriptId, true); const webSockets = useWebSockets(details.params.transcriptId); const { audioDevices, getAudioStream } = useAudioDevice(); diff --git a/www/app/[domain]/transcripts/createTranscript.ts b/www/app/[domain]/transcripts/createTranscript.ts index aaecdc32..31a034f4 100644 --- a/www/app/[domain]/transcripts/createTranscript.ts +++ b/www/app/[domain]/transcripts/createTranscript.ts @@ -19,10 +19,10 @@ const useCreateTranscript = (): CreateTranscript => { const [loading, setLoading] = useState(false); const [error, setErrorState] = useState(null); const { setError } = useError(); - const api = getApi(); + const api = getApi(true); const create = (params: V1TranscriptsCreateRequest["createTranscript"]) => { - if (loading) return; + if (loading || !api) return; setLoading(true); const requestParameters: V1TranscriptsCreateRequest = { diff --git a/www/app/[domain]/transcripts/recorder.tsx b/www/app/[domain]/transcripts/recorder.tsx index 401b6c9e..765d8f09 100644 --- a/www/app/[domain]/transcripts/recorder.tsx +++ b/www/app/[domain]/transcripts/recorder.tsx @@ -15,6 +15,7 @@ import AudioInputsDropdown from "./audioInputsDropdown"; import { Option } from "react-dropdown"; import { useError } from "../../(errors)/errorContext"; import { waveSurferStyles } from "../../styles/recorder"; +import useMp3 from "./useMp3"; type RecorderProps = { setStream?: React.Dispatch>; @@ -29,6 +30,7 @@ type RecorderProps = { waveform?: AudioWaveform | null; isPastMeeting: boolean; transcriptId?: string | null; + mp3Blob?: Blob | null; }; export default function Recorder(props: RecorderProps) { @@ -107,11 +109,7 @@ export default function Recorder(props: RecorderProps) { if (waveformRef.current) { const _wavesurfer = WaveSurfer.create({ container: waveformRef.current, - url: props.transcriptId - ? `${process.env.NEXT_PUBLIC_API_URL}/v1/transcripts/${props.transcriptId}/audio/mp3` - : undefined, peaks: props.waveform?.data, - hideScrollbar: true, autoCenter: true, barWidth: 2, @@ -145,6 +143,10 @@ export default function Recorder(props: RecorderProps) { if (props.isPastMeeting) _wavesurfer.toggleInteraction(true); + if (props.mp3Blob) { + _wavesurfer.loadBlob(props.mp3Blob); + } + setWavesurfer(_wavesurfer); return () => { @@ -156,6 +158,12 @@ export default function Recorder(props: RecorderProps) { } }, []); + useEffect(() => { + if (!wavesurfer) return; + if (!props.mp3Blob) return; + wavesurfer.loadBlob(props.mp3Blob); + }, [props.mp3Blob]); + useEffect(() => { topicsRef.current = props.topics; if (!isRecording) renderMarkers(); diff --git a/www/app/[domain]/transcripts/useMp3.ts b/www/app/[domain]/transcripts/useMp3.ts index 8bccf903..17ac47e2 100644 --- a/www/app/[domain]/transcripts/useMp3.ts +++ b/www/app/[domain]/transcripts/useMp3.ts @@ -1,36 +1,68 @@ -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { DefaultApi, - V1TranscriptGetAudioMp3Request, + // V1TranscriptGetAudioMp3Request, } from "../../api/apis/DefaultApi"; import {} from "../../api"; import { useError } from "../../(errors)/errorContext"; +import { DomainContext } from "../domainContext"; +import getApi from "../../lib/getApi"; +import { useFiefAccessTokenInfo } from "@fief/fief/build/esm/nextjs/react"; type Mp3Response = { url: string | null; + blob: Blob | null; loading: boolean; error: Error | null; }; -const useMp3 = (api: DefaultApi, id: string): Mp3Response => { +const useMp3 = (protectedPath: boolean, id: string): Mp3Response => { const [url, setUrl] = useState(null); + const [blob, setBlob] = useState(null); const [loading, setLoading] = useState(false); const [error, setErrorState] = useState(null); const { setError } = useError(); + const api = getApi(protectedPath); + const { api_url } = useContext(DomainContext); + const accessTokenInfo = useFiefAccessTokenInfo(); const getMp3 = (id: string) => { - if (!id) throw new Error("Transcript ID is required to get transcript Mp3"); + if (!id || !api) return; setLoading(true); - const requestParameters: V1TranscriptGetAudioMp3Request = { - transcriptId: id, - }; - api - .v1TranscriptGetAudioMp3(requestParameters) - .then((result) => { - setUrl(result); - setLoading(false); - console.debug("Transcript Mp3 loaded:", result); + // XXX Current API interface does not output a blob, we need to to is manually + // const requestParameters: V1TranscriptGetAudioMp3Request = { + // transcriptId: id, + // }; + // api + // .v1TranscriptGetAudioMp3(requestParameters) + // .then((result) => { + // setUrl(result); + // setLoading(false); + // console.debug("Transcript Mp3 loaded:", result); + // }) + // .catch((err) => { + // setError(err); + // setErrorState(err); + // }); + const localUrl = `${api_url}/v1/transcripts/${id}/audio/mp3`; + if (localUrl == url) return; + const headers = new Headers(); + + if (accessTokenInfo) { + headers.set("Authorization", "Bearer " + accessTokenInfo.access_token); + } + + fetch(localUrl, { + method: "GET", + headers, + }) + .then((response) => { + setUrl(localUrl); + response.blob().then((blob) => { + setBlob(blob); + setLoading(false); + }); }) .catch((err) => { setError(err); @@ -40,9 +72,9 @@ const useMp3 = (api: DefaultApi, id: string): Mp3Response => { useEffect(() => { getMp3(id); - }, [id]); + }, [id, api]); - return { url, loading, error }; + return { url, blob, loading, error }; }; export default useMp3; diff --git a/www/app/[domain]/transcripts/useTopics.ts b/www/app/[domain]/transcripts/useTopics.ts index 58aebcec..f9299858 100644 --- a/www/app/[domain]/transcripts/useTopics.ts +++ b/www/app/[domain]/transcripts/useTopics.ts @@ -5,6 +5,7 @@ import { } from "../../api/apis/DefaultApi"; import { useError } from "../../(errors)/errorContext"; import { Topic } from "./webSocketTypes"; +import getApi from "../../lib/getApi"; type TranscriptTopics = { topics: Topic[] | null; @@ -12,14 +13,15 @@ type TranscriptTopics = { error: Error | null; }; -const useTopics = (api: DefaultApi, id: string): TranscriptTopics => { +const useTopics = (protectedPath, id: string): TranscriptTopics => { const [topics, setTopics] = useState(null); const [loading, setLoading] = useState(false); const [error, setErrorState] = useState(null); const { setError } = useError(); + const api = getApi(protectedPath); - const getTopics = (id: string) => { - if (!id) return; + useEffect(() => { + if (!id || !api) return; setLoading(true); const requestParameters: V1TranscriptGetTopicsRequest = { @@ -36,11 +38,7 @@ const useTopics = (api: DefaultApi, id: string): TranscriptTopics => { setError(err); setErrorState(err); }); - }; - - useEffect(() => { - getTopics(id); - }, [id]); + }, [id, api]); return { topics, loading, error }; }; diff --git a/www/app/[domain]/transcripts/useTranscript.ts b/www/app/[domain]/transcripts/useTranscript.ts index 81c9202f..1fe462da 100644 --- a/www/app/[domain]/transcripts/useTranscript.ts +++ b/www/app/[domain]/transcripts/useTranscript.ts @@ -1,7 +1,8 @@ import { useEffect, useState } from "react"; -import { DefaultApi, V1TranscriptGetRequest } from "../../api/apis/DefaultApi"; +import { V1TranscriptGetRequest } from "../../api/apis/DefaultApi"; import { GetTranscript } from "../../api"; import { useError } from "../../(errors)/errorContext"; +import getApi from "../../lib/getApi"; type Transcript = { response: GetTranscript | null; @@ -9,14 +10,18 @@ type Transcript = { error: Error | null; }; -const useTranscript = (api: DefaultApi, id: string | null): Transcript => { +const useTranscript = ( + protectedPath: boolean, + id: string | null, +): Transcript => { const [response, setResponse] = useState(null); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [error, setErrorState] = useState(null); const { setError } = useError(); + const api = getApi(protectedPath); - const getTranscript = (id: string | null) => { - if (!id) return; + useEffect(() => { + if (!id || !api) return; setLoading(true); const requestParameters: V1TranscriptGetRequest = { @@ -33,11 +38,7 @@ const useTranscript = (api: DefaultApi, id: string | null): Transcript => { setError(err); setErrorState(err); }); - }; - - useEffect(() => { - getTranscript(id); - }, [id]); + }, [id, !api]); return { response, loading, error }; }; diff --git a/www/app/[domain]/transcripts/useTranscriptList.ts b/www/app/[domain]/transcripts/useTranscriptList.ts new file mode 100644 index 00000000..cc8f4701 --- /dev/null +++ b/www/app/[domain]/transcripts/useTranscriptList.ts @@ -0,0 +1,44 @@ +import { useEffect, useState } from "react"; +import { GetTranscriptFromJSON, PageGetTranscript } from "../../api"; +import { useError } from "../../(errors)/errorContext"; +import getApi from "../../lib/getApi"; + +type TranscriptList = { + response: PageGetTranscript | null; + loading: boolean; + error: Error | null; +}; + +//always protected +const useTranscriptList = (page: number): TranscriptList => { + const [response, setResponse] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setErrorState] = useState(null); + const { setError } = useError(); + const api = getApi(true); + + useEffect(() => { + if (!api) return; + setLoading(true); + api + .v1TranscriptsList({ page }) + .then((response) => { + // issue with API layer, conversion for items is not happening + response.items = response.items.map((item) => + GetTranscriptFromJSON(item), + ); + setResponse(response); + setLoading(false); + }) + .catch((err) => { + setResponse(null); + setLoading(false); + setError(err); + setErrorState(err); + }); + }, [!api, page]); + + return { response, loading, error }; +}; + +export default useTranscriptList; diff --git a/www/app/[domain]/transcripts/useWaveform.ts b/www/app/[domain]/transcripts/useWaveform.ts index 2cc22349..4be63a3a 100644 --- a/www/app/[domain]/transcripts/useWaveform.ts +++ b/www/app/[domain]/transcripts/useWaveform.ts @@ -5,6 +5,7 @@ import { } from "../../api/apis/DefaultApi"; import { AudioWaveform } from "../../api"; import { useError } from "../../(errors)/errorContext"; +import getApi from "../../lib/getApi"; type AudioWaveFormResponse = { waveform: AudioWaveform | null; @@ -12,14 +13,15 @@ type AudioWaveFormResponse = { error: Error | null; }; -const useWaveform = (api: DefaultApi, id: string): AudioWaveFormResponse => { +const useWaveform = (protectedPath, id: string): AudioWaveFormResponse => { const [waveform, setWaveform] = useState(null); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [error, setErrorState] = useState(null); const { setError } = useError(); + const api = getApi(protectedPath); - const getWaveform = (id: string) => { - if (!id) return; + useEffect(() => { + if (!id || !api) return; setLoading(true); const requestParameters: V1TranscriptGetAudioWaveformRequest = { @@ -36,11 +38,7 @@ const useWaveform = (api: DefaultApi, id: string): AudioWaveFormResponse => { setError(err); setErrorState(err); }); - }; - - useEffect(() => { - getWaveform(id); - }, [id]); + }, [id, api]); return { waveform, loading, error }; }; diff --git a/www/app/[domain]/transcripts/useWebRTC.ts b/www/app/[domain]/transcripts/useWebRTC.ts index 07468547..61d2c408 100644 --- a/www/app/[domain]/transcripts/useWebRTC.ts +++ b/www/app/[domain]/transcripts/useWebRTC.ts @@ -5,14 +5,16 @@ import { V1TranscriptRecordWebrtcRequest, } from "../../api/apis/DefaultApi"; import { useError } from "../../(errors)/errorContext"; +import getApi from "../../lib/getApi"; const useWebRTC = ( stream: MediaStream | null, transcriptId: string | null, - api: DefaultApi, + protectedPath, ): Peer => { const [peer, setPeer] = useState(null); const { setError } = useError(); + const api = getApi(protectedPath); useEffect(() => { if (!stream || !transcriptId) { @@ -35,6 +37,7 @@ const useWebRTC = ( }); p.on("signal", (data: any) => { + if (!api) return; if ("sdp" in data) { const requestParameters: V1TranscriptRecordWebrtcRequest = { transcriptId: transcriptId, diff --git a/www/app/lib/edgeConfig.ts b/www/app/lib/edgeConfig.ts index 5527121a..83037725 100644 --- a/www/app/lib/edgeConfig.ts +++ b/www/app/lib/edgeConfig.ts @@ -3,7 +3,7 @@ import { isDevelopment } from "./utils"; const localConfig = { features: { - requireLogin: true, + requireLogin: false, privacy: true, browse: true, }, diff --git a/www/app/lib/getApi.ts b/www/app/lib/getApi.ts index 9347d9b8..ded7da53 100644 --- a/www/app/lib/getApi.ts +++ b/www/app/lib/getApi.ts @@ -2,21 +2,32 @@ import { Configuration } from "../api/runtime"; import { DefaultApi } from "../api/apis/DefaultApi"; import { useFiefAccessTokenInfo } from "@fief/fief/nextjs/react"; -import { useContext } from "react"; -import { DomainContext } from "../[domain]/domainContext"; +import { useContext, useEffect, useState } from "react"; +import { DomainContext, featureEnabled } from "../[domain]/domainContext"; -export default function getApi(): DefaultApi { +export default function getApi(protectedPath: boolean): DefaultApi | undefined { const accessTokenInfo = useFiefAccessTokenInfo(); const api_url = useContext(DomainContext).api_url; + const requireLogin = featureEnabled("requireLogin"); + const [api, setApi] = useState(); + if (!api_url) throw new Error("no API URL"); - const apiConfiguration = new Configuration({ - basePath: api_url, - accessToken: accessTokenInfo - ? "Bearer " + accessTokenInfo.access_token - : undefined, - }); - const api = new DefaultApi(apiConfiguration); + useEffect(() => { + // console.log('trying auth', protectedPath, requireLogin, accessTokenInfo) + if (protectedPath && requireLogin && !accessTokenInfo) { + // console.log('waiting auth') + return; + } + + const apiConfiguration = new Configuration({ + basePath: api_url, + accessToken: accessTokenInfo + ? "Bearer " + accessTokenInfo.access_token + : undefined, + }); + setApi(new DefaultApi(apiConfiguration)); + }, [!accessTokenInfo, protectedPath]); return api; } diff --git a/www/package.json b/www/package.json index edbc0790..55c7df73 100644 --- a/www/package.json +++ b/www/package.json @@ -35,7 +35,7 @@ "supports-color": "^9.4.0", "tailwindcss": "^3.3.2", "typescript": "^5.1.6", - "wavesurfer.js": "^7.0.3" + "wavesurfer.js": "^7.4.2" }, "main": "index.js", "repository": "https://github.com/Monadical-SAS/reflector-ui.git", diff --git a/www/yarn.lock b/www/yarn.lock index a67822be..8ec03382 100644 --- a/www/yarn.lock +++ b/www/yarn.lock @@ -2638,10 +2638,10 @@ watchpack@2.4.0: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" -wavesurfer.js@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.0.3.tgz" - integrity sha512-gJ3P+Bd3Q4E8qETjjg0pneaVqm2J7jegG2Cc6vqEF5YDDKQ3m8sKsvVfgVhJkacKkO9jFAGDu58Hw4zLr7xD0A== +wavesurfer.js@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/wavesurfer.js/-/wavesurfer.js-7.4.2.tgz#59f5c87193d4eeeb199858688ddac1ad7ba86b3a" + integrity sha512-4pNQ1porOCUBYBmd2F1TqVuBnB2wBPipaw2qI920zYLuPnada0Rd1CURgh8HRuPGKxijj2iyZDFN2UZwsaEuhA== wcwidth@>=1.0.1, wcwidth@^1.0.1: version "1.0.1"