mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-23 05:39:05 +00:00
Merge branch 'main' of github.com:Monadical-SAS/reflector into sara/feat-speaker-reassign
This commit is contained in:
@@ -66,18 +66,18 @@ export default function TranscriptBrowser() {
|
||||
<></>
|
||||
)}
|
||||
|
||||
{item.sourceLanguage ? (
|
||||
{item.source_language ? (
|
||||
<div className="inline-block bg-blue-500 text-white px-2 py-1 rounded-full text-xs font-semibold">
|
||||
{item.sourceLanguage}
|
||||
{item.source_language}
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-700">
|
||||
{new Date(item.createdAt).toLocaleDateString("en-US")}
|
||||
{new Date(item.created_at).toLocaleDateString("en-US")}
|
||||
</div>
|
||||
<div className="text-sm">{item.shortSummary}</div>
|
||||
<div className="text-sm">{item.short_summary}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Providers } from "../providers";
|
||||
const poppins = Poppins({ subsets: ["latin"], weight: ["200", "400", "600"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(process.env.DEV_URL || "https://reflector.media"),
|
||||
title: {
|
||||
template: "%s – Reflector",
|
||||
default: "Reflector - AI-Powered Meeting Transcriptions by Monadical",
|
||||
@@ -55,12 +56,6 @@ export const metadata: Metadata = {
|
||||
shortcut: "/r-icon.png",
|
||||
apple: "/r-icon.png",
|
||||
},
|
||||
viewport: {
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
maximumScale: 1,
|
||||
},
|
||||
|
||||
robots: { index: false, follow: false, noarchive: true, noimageindex: true },
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import useTopicWithWords from "../../useTopicWithWords";
|
||||
import ParticipantList from "./participantList";
|
||||
import { GetTranscriptTopic } from "../../../../api";
|
||||
import { SelectedText, selectedTextIsTimeSlice } from "./types";
|
||||
import getApi from "../../../../lib/getApi";
|
||||
import useApi from "../../../../lib/useApi";
|
||||
import useTranscript from "../../useTranscript";
|
||||
import { useError } from "../../../../(errors)/errorContext";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -23,7 +23,7 @@ export type TranscriptCorrect = {
|
||||
export default function TranscriptCorrect({
|
||||
params: { transcriptId },
|
||||
}: TranscriptCorrect) {
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
const transcript = useTranscript(transcriptId);
|
||||
const stateCurrentTopic = useState<GetTranscriptTopic>();
|
||||
const [currentTopic, _sct] = stateCurrentTopic;
|
||||
@@ -37,10 +37,7 @@ export default function TranscriptCorrect({
|
||||
const markAsDone = () => {
|
||||
if (transcript.response && !transcript.response.reviewed) {
|
||||
api
|
||||
?.v1TranscriptUpdate({
|
||||
transcriptId,
|
||||
updateTranscript: { reviewed: true },
|
||||
})
|
||||
?.v1TranscriptUpdate(transcriptId, { reviewed: true })
|
||||
.then(() => {
|
||||
router.push(`/transcripts/${transcriptId}`);
|
||||
})
|
||||
@@ -75,7 +72,7 @@ export default function TranscriptCorrect({
|
||||
currentTopic
|
||||
? {
|
||||
start: currentTopic?.timestamp,
|
||||
end: currentTopic?.timestamp + currentTopic?.duration,
|
||||
end: currentTopic?.timestamp + (currentTopic?.duration || 0),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { faArrowTurnDown, faSpinner } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import { Participant } from "../../../../api";
|
||||
import getApi from "../../../../lib/getApi";
|
||||
import useApi from "../../../../lib/useApi";
|
||||
import { UseParticipants } from "../../useParticipants";
|
||||
import { selectedTextIsSpeaker, selectedTextIsTimeSlice } from "./types";
|
||||
import { useError } from "../../../../(errors)/errorContext";
|
||||
@@ -31,7 +31,7 @@ const ParticipantList = ({
|
||||
topicWithWords,
|
||||
stateSelectedText,
|
||||
}: ParticipantList) => {
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
const { setError } = useError();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [participantInput, setParticipantInput] = useState("");
|
||||
@@ -123,13 +123,10 @@ const ParticipantList = ({
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
await api?.v1TranscriptAssignSpeaker({
|
||||
speakerAssignment: {
|
||||
participant: participant.id,
|
||||
timestampFrom: selectedText.start,
|
||||
timestampTo: selectedText.end,
|
||||
},
|
||||
transcriptId,
|
||||
await api?.v1TranscriptAssignSpeaker(transcriptId, {
|
||||
participant: participant.id,
|
||||
timestamp_from: selectedText.start,
|
||||
timestamp_to: selectedText.end,
|
||||
});
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
@@ -145,12 +142,9 @@ const ParticipantList = ({
|
||||
setLoading(true);
|
||||
if (participantTo.speaker) {
|
||||
try {
|
||||
await api?.v1TranscriptMergeSpeaker({
|
||||
transcriptId,
|
||||
speakerMerge: {
|
||||
speakerFrom: speakerFrom,
|
||||
speakerTo: participantTo.speaker,
|
||||
},
|
||||
await api?.v1TranscriptMergeSpeaker(transcriptId, {
|
||||
speaker_from: speakerFrom,
|
||||
speaker_to: participantTo.speaker,
|
||||
});
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
@@ -159,11 +153,11 @@ const ParticipantList = ({
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await api?.v1TranscriptUpdateParticipant({
|
||||
await api?.v1TranscriptUpdateParticipant(
|
||||
transcriptId,
|
||||
participantId: participantTo.id,
|
||||
updateParticipant: { speaker: speakerFrom },
|
||||
});
|
||||
participantTo.id,
|
||||
{ speaker: speakerFrom },
|
||||
);
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
setError(error, "There was an error merging (update)");
|
||||
@@ -189,12 +183,8 @@ const ParticipantList = ({
|
||||
if (participant && participant.name !== participantInput) {
|
||||
setLoading(true);
|
||||
api
|
||||
?.v1TranscriptUpdateParticipant({
|
||||
participantId: participant.id,
|
||||
transcriptId,
|
||||
updateParticipant: {
|
||||
name: participantInput,
|
||||
},
|
||||
?.v1TranscriptUpdateParticipant(participant.id, transcriptId, {
|
||||
name: participantInput,
|
||||
})
|
||||
.then(() => {
|
||||
participants.refetch();
|
||||
@@ -212,12 +202,9 @@ const ParticipantList = ({
|
||||
) {
|
||||
setLoading(true);
|
||||
api
|
||||
?.v1TranscriptAddParticipant({
|
||||
createParticipant: {
|
||||
name: participantInput,
|
||||
speaker: selectedText,
|
||||
},
|
||||
transcriptId,
|
||||
?.v1TranscriptAddParticipant(transcriptId, {
|
||||
name: participantInput,
|
||||
speaker: selectedText,
|
||||
})
|
||||
.then(() => {
|
||||
participants.refetch();
|
||||
@@ -235,12 +222,12 @@ const ParticipantList = ({
|
||||
) {
|
||||
setLoading(true);
|
||||
try {
|
||||
const participant = await api?.v1TranscriptAddParticipant({
|
||||
createParticipant: {
|
||||
const participant = await api?.v1TranscriptAddParticipant(
|
||||
transcriptId,
|
||||
{
|
||||
name: participantInput,
|
||||
},
|
||||
transcriptId,
|
||||
});
|
||||
);
|
||||
setLoading(false);
|
||||
assignTo(participant)().catch(() => {
|
||||
// error and loading are handled by assignTo catch
|
||||
@@ -253,11 +240,8 @@ const ParticipantList = ({
|
||||
} else if (action == "Create") {
|
||||
setLoading(true);
|
||||
api
|
||||
?.v1TranscriptAddParticipant({
|
||||
createParticipant: {
|
||||
name: participantInput,
|
||||
},
|
||||
transcriptId,
|
||||
?.v1TranscriptAddParticipant(transcriptId, {
|
||||
name: participantInput,
|
||||
})
|
||||
.then(() => {
|
||||
participants.refetch();
|
||||
@@ -277,10 +261,7 @@ const ParticipantList = ({
|
||||
if (loading || participants.loading || topicWithWords.loading) return;
|
||||
setLoading(true);
|
||||
api
|
||||
?.v1TranscriptDeleteParticipant({
|
||||
transcriptId,
|
||||
participantId,
|
||||
})
|
||||
?.v1TranscriptDeleteParticipant(transcriptId, participantId)
|
||||
.then(() => {
|
||||
participants.refetch();
|
||||
setLoading(false);
|
||||
|
||||
@@ -9,9 +9,7 @@ import {
|
||||
Kbd,
|
||||
Skeleton,
|
||||
SkeletonCircle,
|
||||
chakra,
|
||||
Flex,
|
||||
Center,
|
||||
} from "@chakra-ui/react";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "@chakra-ui/icons";
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
WrapItem,
|
||||
Kbd,
|
||||
Skeleton,
|
||||
chakra,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
type TopicPlayer = {
|
||||
|
||||
@@ -3,14 +3,7 @@ import WaveformLoading from "../../waveformLoading";
|
||||
import { UseParticipants } from "../../useParticipants";
|
||||
import { UseTopicWithWords } from "../../useTopicWithWords";
|
||||
import { TimeSlice, selectedTextIsTimeSlice } from "./types";
|
||||
import {
|
||||
BoxProps,
|
||||
Box,
|
||||
Container,
|
||||
Text,
|
||||
chakra,
|
||||
Spinner,
|
||||
} from "@chakra-ui/react";
|
||||
import { BoxProps, Box, Container, Text, Spinner } from "@chakra-ui/react";
|
||||
|
||||
type TopicWordsProps = {
|
||||
stateSelectedText: [
|
||||
@@ -167,7 +160,7 @@ const topicWords = ({
|
||||
maxW={{ lg: "container.md" }}
|
||||
{...chakraProps}
|
||||
>
|
||||
{topicWithWords.response.wordsPerSpeaker.map(
|
||||
{topicWithWords.response.words_per_speaker?.map(
|
||||
(speakerWithWords, index) => (
|
||||
<Text key={index} className="mb-2 last:mb-0">
|
||||
<Box
|
||||
|
||||
@@ -17,6 +17,7 @@ import Player from "../player";
|
||||
import WaveformLoading from "../waveformLoading";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { featureEnabled } from "../../domainContext";
|
||||
import { toShareMode } from "../../../lib/shareMode";
|
||||
|
||||
type TranscriptDetails = {
|
||||
params: {
|
||||
@@ -37,7 +38,7 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
||||
|
||||
useEffect(() => {
|
||||
const statusToRedirect = ["idle", "recording", "processing"];
|
||||
if (statusToRedirect.includes(transcript.response?.status)) {
|
||||
if (statusToRedirect.includes(transcript.response?.status || "")) {
|
||||
const newUrl = "/transcripts/" + details.params.transcriptId + "/record";
|
||||
// Shallow redirection does not work on NextJS 13
|
||||
// https://github.com/vercel/next.js/discussions/48110
|
||||
@@ -88,7 +89,7 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
||||
<Player
|
||||
topics={topics?.topics || []}
|
||||
useActiveTopic={useActiveTopic}
|
||||
waveform={waveform.waveform.data}
|
||||
waveform={waveform.waveform}
|
||||
media={mp3.media}
|
||||
mediaDuration={transcript.response.duration}
|
||||
/>
|
||||
@@ -108,10 +109,10 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
||||
|
||||
<div className="w-full h-full grid grid-rows-layout-one grid-cols-1 gap-2 lg:gap-4">
|
||||
<section className=" bg-blue-400/20 rounded-lg md:rounded-xl p-2 md:px-4 h-full">
|
||||
{transcript.response.longSummary ? (
|
||||
{transcript.response.long_summary ? (
|
||||
<FinalSummary
|
||||
fullTranscript={fullTranscript}
|
||||
summary={transcript.response.longSummary}
|
||||
summary={transcript.response.long_summary}
|
||||
transcriptId={transcript.response.id}
|
||||
openZulipModal={() => setShowModal(true)}
|
||||
/>
|
||||
@@ -139,9 +140,9 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
||||
</div>
|
||||
<div className="flex-grow max-w-full">
|
||||
<ShareLink
|
||||
transcriptId={transcript?.response?.id}
|
||||
userId={transcript?.response?.userId}
|
||||
shareMode={transcript?.response?.shareMode}
|
||||
transcriptId={transcript.response.id}
|
||||
userId={transcript.response.user_id}
|
||||
shareMode={toShareMode(transcript.response.share_mode)}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -62,8 +62,9 @@ const TranscriptRecord = (details: TranscriptDetails) => {
|
||||
|
||||
//TODO if has no topic and is error, get back to new
|
||||
if (
|
||||
statusToRedirect.includes(transcript.response?.status) ||
|
||||
statusToRedirect.includes(webSockets.status.value)
|
||||
transcript.response?.status &&
|
||||
(statusToRedirect.includes(transcript.response?.status) ||
|
||||
statusToRedirect.includes(webSockets.status.value))
|
||||
) {
|
||||
const newUrl = "/transcripts/" + details.params.transcriptId;
|
||||
// Shallow redirection does not work on NextJS 13
|
||||
@@ -75,10 +76,8 @@ const TranscriptRecord = (details: TranscriptDetails) => {
|
||||
}, [webSockets.status.value, transcript.response?.status]);
|
||||
|
||||
useEffect(() => {
|
||||
if (webSockets.duration) {
|
||||
mp3.getNow();
|
||||
}
|
||||
}, [webSockets.duration]);
|
||||
if (transcript.response?.status === "ended") mp3.getNow();
|
||||
}, [transcript.response]);
|
||||
|
||||
useEffect(() => {
|
||||
lockWakeState();
|
||||
@@ -112,6 +111,7 @@ const TranscriptRecord = (details: TranscriptDetails) => {
|
||||
}}
|
||||
getAudioStream={getAudioStream}
|
||||
audioDevices={audioDevices}
|
||||
transcriptId={details.params.transcriptId}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,48 +1,32 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
DefaultApi,
|
||||
V1TranscriptsCreateRequest,
|
||||
} from "../../api/apis/DefaultApi";
|
||||
import { GetTranscript } from "../../api";
|
||||
import { useState } from "react";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import getApi from "../../lib/getApi";
|
||||
import { GetTranscript, CreateTranscript } from "../../api";
|
||||
import useApi from "../../lib/useApi";
|
||||
|
||||
type CreateTranscript = {
|
||||
response: GetTranscript | null;
|
||||
type UseTranscript = {
|
||||
transcript: GetTranscript | null;
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
create: (params: V1TranscriptsCreateRequest["createTranscript"]) => void;
|
||||
create: (transcriptCreationDetails: CreateTranscript) => void;
|
||||
};
|
||||
|
||||
const useCreateTranscript = (): CreateTranscript => {
|
||||
const [response, setResponse] = useState<GetTranscript | null>(null);
|
||||
const useCreateTranscript = (): UseTranscript => {
|
||||
const [transcript, setTranscript] = useState<GetTranscript | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setErrorState] = useState<Error | null>(null);
|
||||
const { setError } = useError();
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
|
||||
const create = (params: V1TranscriptsCreateRequest["createTranscript"]) => {
|
||||
const create = (transcriptCreationDetails: CreateTranscript) => {
|
||||
if (loading || !api) return;
|
||||
|
||||
setLoading(true);
|
||||
const requestParameters: V1TranscriptsCreateRequest = {
|
||||
createTranscript: {
|
||||
name: params.name || "Unnamed Transcript", // Default
|
||||
targetLanguage: params.targetLanguage || "en", // Default
|
||||
},
|
||||
};
|
||||
|
||||
console.debug(
|
||||
"POST - /v1/transcripts/ - Requesting new transcription creation",
|
||||
requestParameters,
|
||||
);
|
||||
|
||||
api
|
||||
.v1TranscriptsCreate(requestParameters)
|
||||
.then((result) => {
|
||||
setResponse(result);
|
||||
.v1TranscriptsCreate(transcriptCreationDetails)
|
||||
.then((transcript) => {
|
||||
setTranscript(transcript);
|
||||
setLoading(false);
|
||||
console.debug("New transcript created:", result);
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(
|
||||
@@ -54,7 +38,7 @@ const useCreateTranscript = (): CreateTranscript => {
|
||||
});
|
||||
};
|
||||
|
||||
return { response, loading, error, create };
|
||||
return { transcript, loading, error, create };
|
||||
};
|
||||
|
||||
export default useCreateTranscript;
|
||||
|
||||
50
www/app/[domain]/transcripts/fileUploadButton.tsx
Normal file
50
www/app/[domain]/transcripts/fileUploadButton.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from "react";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { Body_transcript_record_upload_v1_transcripts__transcript_id__record_upload_post } from "../../api";
|
||||
|
||||
type FileUploadButton = {
|
||||
transcriptId: string;
|
||||
};
|
||||
|
||||
export default function FileUploadButton(props: FileUploadButton) {
|
||||
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
||||
const api = useApi();
|
||||
|
||||
const triggerFileUpload = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
|
||||
if (file) {
|
||||
console.log("Calling api.v1TranscriptRecordUpload()...");
|
||||
|
||||
// Create an object of the expected type
|
||||
const uploadData = {
|
||||
file: file,
|
||||
// Add other properties if required by the type definition
|
||||
};
|
||||
|
||||
api?.v1TranscriptRecordUpload(props.transcriptId, uploadData);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
className="bg-blue-400 hover:bg-blue-500 focus-visible:bg-blue-500 text-white ml-2 md:ml:4 md:h-[78px] md:min-w-[100px] text-lg"
|
||||
onClick={triggerFileUpload}
|
||||
>
|
||||
Upload File
|
||||
</button>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
style={{ display: "none" }}
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,8 +2,9 @@ import { useRef, useState } from "react";
|
||||
import React from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import "../../styles/markdown.css";
|
||||
import getApi from "../../lib/getApi";
|
||||
import { featureEnabled } from "../domainContext";
|
||||
import { UpdateTranscript } from "../../api";
|
||||
import useApi from "../../lib/useApi";
|
||||
|
||||
type FinalSummaryProps = {
|
||||
summary: string;
|
||||
@@ -19,17 +20,17 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [preEditSummary, setPreEditSummary] = useState(props.summary);
|
||||
const [editedSummary, setEditedSummary] = useState(props.summary);
|
||||
const api = getApi();
|
||||
|
||||
const updateSummary = async (newSummary: string, transcriptId: string) => {
|
||||
if (!api) return;
|
||||
try {
|
||||
const updatedTranscript = await api.v1TranscriptUpdate({
|
||||
const api = useApi();
|
||||
const requestBody: UpdateTranscript = {
|
||||
long_summary: newSummary,
|
||||
};
|
||||
const updatedTranscript = await api?.v1TranscriptUpdate(
|
||||
transcriptId,
|
||||
updateTranscript: {
|
||||
longSummary: newSummary,
|
||||
},
|
||||
});
|
||||
requestBody,
|
||||
);
|
||||
console.log("Updated long summary:", updatedTranscript);
|
||||
} catch (err) {
|
||||
console.error("Failed to update long summary:", err);
|
||||
|
||||
@@ -18,7 +18,7 @@ const TranscriptCreate = () => {
|
||||
const isAuthenticated = useFiefIsAuthenticated();
|
||||
const requireLogin = featureEnabled("requireLogin");
|
||||
|
||||
const [name, setName] = useState<string>();
|
||||
const [name, setName] = useState<string>("");
|
||||
const nameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(event.target.value);
|
||||
};
|
||||
@@ -35,13 +35,13 @@ const TranscriptCreate = () => {
|
||||
const send = () => {
|
||||
if (loadingSend || createTranscript.loading || permissionDenied) return;
|
||||
setLoadingSend(true);
|
||||
createTranscript.create({ name, targetLanguage });
|
||||
createTranscript.create({ name, target_language: targetLanguage });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
createTranscript.response &&
|
||||
router.push(`/transcripts/${createTranscript.response.id}/record`);
|
||||
}, [createTranscript.response]);
|
||||
createTranscript.transcript &&
|
||||
router.push(`/transcripts/${createTranscript.transcript.id}/record`);
|
||||
}, [createTranscript.transcript]);
|
||||
|
||||
useEffect(() => {
|
||||
if (createTranscript.error) setLoadingSend(false);
|
||||
@@ -58,6 +58,7 @@ const TranscriptCreate = () => {
|
||||
<h1 className="text-2xl font-bold mb-2">
|
||||
Welcome to reflector.media
|
||||
</h1>
|
||||
<button>Test upload</button>
|
||||
<p>
|
||||
Reflector is a transcription and summarization pipeline that
|
||||
transforms audio into knowledge.
|
||||
|
||||
@@ -14,7 +14,7 @@ type PlayerProps = {
|
||||
Topic | null,
|
||||
React.Dispatch<React.SetStateAction<Topic | null>>,
|
||||
];
|
||||
waveform: AudioWaveform["data"];
|
||||
waveform: AudioWaveform;
|
||||
media: HTMLMediaElement;
|
||||
mediaDuration: number;
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import AudioInputsDropdown from "./audioInputsDropdown";
|
||||
import { Option } from "react-dropdown";
|
||||
import { waveSurferStyles } from "../../styles/recorder";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import FileUploadButton from "./fileUploadButton";
|
||||
|
||||
type RecorderProps = {
|
||||
setStream: React.Dispatch<React.SetStateAction<MediaStream | null>>;
|
||||
@@ -19,6 +20,7 @@ type RecorderProps = {
|
||||
onRecord?: () => void;
|
||||
getAudioStream: (deviceId) => Promise<MediaStream | null>;
|
||||
audioDevices: Option[];
|
||||
transcriptId: string;
|
||||
};
|
||||
|
||||
export default function Recorder(props: RecorderProps) {
|
||||
@@ -307,6 +309,11 @@ export default function Recorder(props: RecorderProps) {
|
||||
>
|
||||
{isRecording ? "Stop" : "Record"}
|
||||
</button>
|
||||
|
||||
<FileUploadButton
|
||||
transcriptId={props.transcriptId}
|
||||
></FileUploadButton>
|
||||
|
||||
{!isRecording && (
|
||||
<button
|
||||
className={`${
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useRef, useEffect, use } from "react";
|
||||
import { featureEnabled } from "../domainContext";
|
||||
import getApi from "../../lib/getApi";
|
||||
import { useFiefUserinfo } from "@fief/fief/nextjs/react";
|
||||
import SelectSearch from "react-select-search";
|
||||
import "react-select-search/style.css";
|
||||
@@ -8,11 +7,13 @@ import "../../styles/button.css";
|
||||
import "../../styles/form.scss";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { UpdateTranscript } from "../../api";
|
||||
import { ShareMode, toShareMode } from "../../lib/shareMode";
|
||||
import useApi from "../../lib/useApi";
|
||||
type ShareLinkProps = {
|
||||
transcriptId: string;
|
||||
userId: string | null;
|
||||
shareMode: string;
|
||||
shareMode: ShareMode;
|
||||
};
|
||||
|
||||
const ShareLink = (props: ShareLinkProps) => {
|
||||
@@ -21,10 +22,10 @@ const ShareLink = (props: ShareLinkProps) => {
|
||||
const [currentUrl, setCurrentUrl] = useState<string>("");
|
||||
const requireLogin = featureEnabled("requireLogin");
|
||||
const [isOwner, setIsOwner] = useState(false);
|
||||
const [shareMode, setShareMode] = useState(props.shareMode);
|
||||
const [shareMode, setShareMode] = useState<ShareMode>(props.shareMode);
|
||||
const [shareLoading, setShareLoading] = useState(false);
|
||||
const userinfo = useFiefUserinfo();
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentUrl(window.location.href);
|
||||
@@ -48,15 +49,19 @@ const ShareLink = (props: ShareLinkProps) => {
|
||||
};
|
||||
|
||||
const updateShareMode = async (selectedShareMode: string) => {
|
||||
if (!api) return;
|
||||
if (!api)
|
||||
throw new Error("ShareLink's API should always be ready at this point");
|
||||
|
||||
setShareLoading(true);
|
||||
const updatedTranscript = await api.v1TranscriptUpdate({
|
||||
transcriptId: props.transcriptId,
|
||||
updateTranscript: {
|
||||
shareMode: selectedShareMode,
|
||||
},
|
||||
});
|
||||
setShareMode(updatedTranscript.shareMode);
|
||||
const requestBody: UpdateTranscript = {
|
||||
share_mode: toShareMode(selectedShareMode),
|
||||
};
|
||||
|
||||
const updatedTranscript = await api.v1TranscriptUpdate(
|
||||
props.transcriptId,
|
||||
requestBody,
|
||||
);
|
||||
setShareMode(toShareMode(updatedTranscript.share_mode));
|
||||
setShareLoading(false);
|
||||
};
|
||||
const privacyEnabled = featureEnabled("privacy");
|
||||
@@ -89,7 +94,7 @@ const ShareLink = (props: ShareLinkProps) => {
|
||||
{ name: "Secure", value: "semi-private" },
|
||||
{ name: "Public", value: "public" },
|
||||
]}
|
||||
value={shareMode}
|
||||
value={shareMode?.toString()}
|
||||
onChange={updateShareMode}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import getApi from "../../lib/getApi";
|
||||
import { UpdateTranscript } from "../../api";
|
||||
import useApi from "../../lib/useApi";
|
||||
|
||||
type TranscriptTitle = {
|
||||
title: string;
|
||||
@@ -10,17 +11,19 @@ const TranscriptTitle = (props: TranscriptTitle) => {
|
||||
const [displayedTitle, setDisplayedTitle] = useState(props.title);
|
||||
const [preEditTitle, setPreEditTitle] = useState(props.title);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
|
||||
const updateTitle = async (newTitle: string, transcriptId: string) => {
|
||||
if (!api) return;
|
||||
try {
|
||||
const updatedTranscript = await api.v1TranscriptUpdate({
|
||||
const requestBody: UpdateTranscript = {
|
||||
title: newTitle,
|
||||
};
|
||||
const api = useApi();
|
||||
const updatedTranscript = await api?.v1TranscriptUpdate(
|
||||
transcriptId,
|
||||
updateTranscript: {
|
||||
title: newTitle,
|
||||
},
|
||||
});
|
||||
requestBody,
|
||||
);
|
||||
console.log("Updated transcript:", updatedTranscript);
|
||||
} catch (err) {
|
||||
console.error("Failed to update transcript:", err);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { DomainContext } from "../domainContext";
|
||||
import getApi from "../../lib/getApi";
|
||||
import getApi from "../../lib/useApi";
|
||||
import { useFiefAccessTokenInfo } from "@fief/fief/build/esm/nextjs/react";
|
||||
|
||||
export type Mp3Response = {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { V1TranscriptGetParticipantsRequest } from "../../api/apis/DefaultApi";
|
||||
import { GetTranscript, Participant } from "../../api";
|
||||
import { Participant } from "../../api";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import getApi from "../../lib/getApi";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { shouldShowError } from "../../lib/errorUtils";
|
||||
|
||||
type ErrorParticipants = {
|
||||
@@ -34,7 +33,7 @@ const useParticipants = (transcriptId: string): UseParticipants => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setErrorState] = useState<Error | null>(null);
|
||||
const { setError } = useError();
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
const refetch = () => {
|
||||
@@ -49,11 +48,8 @@ const useParticipants = (transcriptId: string): UseParticipants => {
|
||||
if (!transcriptId || !api) return;
|
||||
|
||||
setLoading(true);
|
||||
const requestParameters: V1TranscriptGetParticipantsRequest = {
|
||||
transcriptId,
|
||||
};
|
||||
api
|
||||
.v1TranscriptGetParticipants(requestParameters)
|
||||
.v1TranscriptGetParticipants(transcriptId)
|
||||
.then((result) => {
|
||||
setResponse(result);
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
V1TranscriptGetTopicsWithWordsPerSpeakerRequest,
|
||||
V1TranscriptGetTopicsWithWordsRequest,
|
||||
} from "../../api/apis/DefaultApi";
|
||||
import {
|
||||
GetTranscript,
|
||||
GetTranscriptTopicWithWords,
|
||||
GetTranscriptTopicWithWordsPerSpeaker,
|
||||
} from "../../api";
|
||||
|
||||
import { GetTranscriptTopicWithWordsPerSpeaker } from "../../api";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import getApi from "../../lib/getApi";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { shouldShowError } from "../../lib/errorUtils";
|
||||
|
||||
type ErrorTopicWithWords = {
|
||||
@@ -37,7 +30,7 @@ export type UseTopicWithWords = { refetch: () => void } & (
|
||||
);
|
||||
|
||||
const useTopicWithWords = (
|
||||
topicId: string | null,
|
||||
topicId: string | undefined,
|
||||
transcriptId: string,
|
||||
): UseTopicWithWords => {
|
||||
const [response, setResponse] =
|
||||
@@ -45,7 +38,7 @@ const useTopicWithWords = (
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setErrorState] = useState<Error | null>(null);
|
||||
const { setError } = useError();
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
@@ -61,12 +54,9 @@ const useTopicWithWords = (
|
||||
if (!transcriptId || !topicId || !api) return;
|
||||
|
||||
setLoading(true);
|
||||
const requestParameters: V1TranscriptGetTopicsWithWordsPerSpeakerRequest = {
|
||||
transcriptId,
|
||||
topicId,
|
||||
};
|
||||
|
||||
api
|
||||
.v1TranscriptGetTopicsWithWordsPerSpeaker(requestParameters)
|
||||
.v1TranscriptGetTopicsWithWordsPerSpeaker(transcriptId, topicId)
|
||||
.then((result) => {
|
||||
setResponse(result);
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { V1TranscriptGetTopicsRequest } from "../../api/apis/DefaultApi";
|
||||
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import { Topic } from "./webSocketTypes";
|
||||
import getApi from "../../lib/getApi";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { shouldShowError } from "../../lib/errorUtils";
|
||||
import mockTopics from "./mockTopics.json";
|
||||
import { GetTranscriptTopic } from "../../api";
|
||||
|
||||
type TranscriptTopics = {
|
||||
@@ -18,25 +17,14 @@ const useTopics = (id: string): TranscriptTopics => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setErrorState] = useState<Error | null>(null);
|
||||
const { setError } = useError();
|
||||
const api = getApi();
|
||||
|
||||
useEffect(() => {
|
||||
document.onkeyup = (e) => {
|
||||
if (e.key === "t" && process.env.NEXT_PUBLIC_ENV === "development") {
|
||||
setTopics(mockTopics);
|
||||
}
|
||||
};
|
||||
});
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (!id || !api) return;
|
||||
|
||||
setLoading(true);
|
||||
const requestParameters: V1TranscriptGetTopicsRequest = {
|
||||
transcriptId: id,
|
||||
};
|
||||
api
|
||||
.v1TranscriptGetTopics(requestParameters)
|
||||
.v1TranscriptGetTopics(id)
|
||||
.then((result) => {
|
||||
setTopics(result);
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { V1TranscriptGetRequest } from "../../api/apis/DefaultApi";
|
||||
import { GetTranscript } from "../../api";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import getApi from "../../lib/getApi";
|
||||
import { shouldShowError } from "../../lib/errorUtils";
|
||||
import useApi from "../../lib/useApi";
|
||||
|
||||
type ErrorTranscript = {
|
||||
error: Error;
|
||||
@@ -30,17 +29,15 @@ const useTranscript = (
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setErrorState] = useState<Error | null>(null);
|
||||
const { setError } = useError();
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (!id || !api) return;
|
||||
|
||||
setLoading(true);
|
||||
const requestParameters: V1TranscriptGetRequest = {
|
||||
transcriptId: id,
|
||||
};
|
||||
|
||||
api
|
||||
.v1TranscriptGet(requestParameters)
|
||||
.v1TranscriptGet(id)
|
||||
.then((result) => {
|
||||
setResponse(result);
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { GetTranscriptFromJSON, PageGetTranscript } from "../../api";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import getApi from "../../lib/getApi";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { Page_GetTranscript_ } from "../../api";
|
||||
|
||||
type TranscriptList = {
|
||||
response: PageGetTranscript | null;
|
||||
response: Page_GetTranscript_ | null;
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
};
|
||||
|
||||
//always protected
|
||||
const useTranscriptList = (page: number): TranscriptList => {
|
||||
const [response, setResponse] = useState<PageGetTranscript | null>(null);
|
||||
const [response, setResponse] = useState<Page_GetTranscript_ | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setErrorState] = useState<Error | null>(null);
|
||||
const { setError } = useError();
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (!api) return;
|
||||
setLoading(true);
|
||||
api
|
||||
.v1TranscriptsList({ page })
|
||||
.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);
|
||||
})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { V1TranscriptGetAudioWaveformRequest } from "../../api/apis/DefaultApi";
|
||||
import { AudioWaveform } from "../../api";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import getApi from "../../lib/getApi";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { shouldShowError } from "../../lib/errorUtils";
|
||||
|
||||
type AudioWaveFormResponse = {
|
||||
@@ -16,16 +15,13 @@ const useWaveform = (id: string): AudioWaveFormResponse => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setErrorState] = useState<Error | null>(null);
|
||||
const { setError } = useError();
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (!id || !api) return;
|
||||
setLoading(true);
|
||||
const requestParameters: V1TranscriptGetAudioWaveformRequest = {
|
||||
transcriptId: id,
|
||||
};
|
||||
api
|
||||
.v1TranscriptGetAudioWaveform(requestParameters)
|
||||
.v1TranscriptGetAudioWaveform(id)
|
||||
.then((result) => {
|
||||
setWaveform(result);
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Peer from "simple-peer";
|
||||
import {
|
||||
DefaultApi,
|
||||
V1TranscriptRecordWebrtcRequest,
|
||||
} from "../../api/apis/DefaultApi";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import getApi from "../../lib/getApi";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { RtcOffer } from "../../api";
|
||||
|
||||
const useWebRTC = (
|
||||
stream: MediaStream | null,
|
||||
@@ -13,7 +10,7 @@ const useWebRTC = (
|
||||
): Peer => {
|
||||
const [peer, setPeer] = useState<Peer | null>(null);
|
||||
const { setError } = useError();
|
||||
const api = getApi();
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (!stream || !transcriptId) {
|
||||
@@ -38,16 +35,13 @@ const useWebRTC = (
|
||||
p.on("signal", (data: any) => {
|
||||
if (!api) return;
|
||||
if ("sdp" in data) {
|
||||
const requestParameters: V1TranscriptRecordWebrtcRequest = {
|
||||
transcriptId: transcriptId,
|
||||
rtcOffer: {
|
||||
sdp: data.sdp,
|
||||
type: data.type,
|
||||
},
|
||||
const rtcOffer: RtcOffer = {
|
||||
sdp: data.sdp,
|
||||
type: data.type,
|
||||
};
|
||||
|
||||
api
|
||||
.v1TranscriptRecordWebrtc(requestParameters)
|
||||
.v1TranscriptRecordWebrtc(transcriptId, rtcOffer)
|
||||
.then((answer) => {
|
||||
try {
|
||||
p.signal(answer);
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useContext, useEffect, useState } from "react";
|
||||
import { Topic, FinalSummary, Status } from "./webSocketTypes";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import { DomainContext } from "../domainContext";
|
||||
import { AudioWaveform } from "../../api";
|
||||
import { AudioWaveform, GetTranscriptSegmentTopic } from "../../api";
|
||||
import useApi from "../../lib/useApi";
|
||||
|
||||
export type UseWebSockets = {
|
||||
transcriptText: string;
|
||||
@@ -11,7 +12,7 @@ export type UseWebSockets = {
|
||||
topics: Topic[];
|
||||
finalSummary: FinalSummary;
|
||||
status: Status;
|
||||
waveform: AudioWaveform["data"] | null;
|
||||
waveform: AudioWaveform | null;
|
||||
duration: number | null;
|
||||
};
|
||||
|
||||
@@ -32,6 +33,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
||||
const { setError } = useError();
|
||||
|
||||
const { websocket_url } = useContext(DomainContext);
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (isProcessing || textQueue.length === 0) {
|
||||
@@ -57,6 +59,39 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
||||
useEffect(() => {
|
||||
document.onkeyup = (e) => {
|
||||
if (e.key === "a" && process.env.NEXT_PUBLIC_ENV === "development") {
|
||||
const segments: GetTranscriptSegmentTopic[] = [
|
||||
{
|
||||
speaker: 1,
|
||||
start: 0,
|
||||
text: "This is the transcription of an example title",
|
||||
},
|
||||
{
|
||||
speaker: 2,
|
||||
start: 10,
|
||||
text: "This is the second speaker",
|
||||
},
|
||||
{
|
||||
speaker: 3,
|
||||
start: 90,
|
||||
text: "This is the third speaker",
|
||||
},
|
||||
{
|
||||
speaker: 4,
|
||||
start: 90,
|
||||
text: "This is the fourth speaker",
|
||||
},
|
||||
{
|
||||
speaker: 5,
|
||||
start: 123,
|
||||
text: "This is the fifth speaker",
|
||||
},
|
||||
{
|
||||
speaker: 6,
|
||||
start: 300,
|
||||
text: "This is the sixth speaker",
|
||||
},
|
||||
];
|
||||
|
||||
setTranscriptText("Lorem Ipsum");
|
||||
setTopics([
|
||||
{
|
||||
@@ -67,39 +102,6 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
||||
title: "Topic 1: Introduction to Quantum Mechanics",
|
||||
transcript:
|
||||
"A brief overview of quantum mechanics and its principles.",
|
||||
segments: [
|
||||
{
|
||||
speaker: 1,
|
||||
start: 0,
|
||||
text: "This is the transcription of an example title",
|
||||
},
|
||||
{
|
||||
speaker: 2,
|
||||
start: 10,
|
||||
text: "This is the second speaker",
|
||||
},
|
||||
,
|
||||
{
|
||||
speaker: 3,
|
||||
start: 90,
|
||||
text: "This is the third speaker",
|
||||
},
|
||||
{
|
||||
speaker: 4,
|
||||
start: 90,
|
||||
text: "This is the fourth speaker",
|
||||
},
|
||||
{
|
||||
speaker: 5,
|
||||
start: 123,
|
||||
text: "This is the fifth speaker",
|
||||
},
|
||||
{
|
||||
speaker: 6,
|
||||
start: 300,
|
||||
text: "This is the sixth speaker",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
@@ -306,7 +308,9 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
||||
}
|
||||
};
|
||||
|
||||
if (!transcriptId) return;
|
||||
if (!transcriptId || !api) return;
|
||||
|
||||
api?.v1TranscriptGetWebsocketEvents(transcriptId).then((result) => {});
|
||||
|
||||
const url = `${websocket_url}/v1/transcripts/${transcriptId}/events`;
|
||||
let ws = new WebSocket(url);
|
||||
@@ -412,7 +416,9 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
||||
console.debug("WebSocket connection closed");
|
||||
switch (event.code) {
|
||||
case 1000: // Normal Closure:
|
||||
break;
|
||||
case 1005: // Closure by client FF
|
||||
break;
|
||||
default:
|
||||
setError(
|
||||
new Error(`WebSocket closed unexpectedly with code: ${event.code}`),
|
||||
@@ -431,7 +437,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
||||
return () => {
|
||||
ws.close();
|
||||
};
|
||||
}, [transcriptId]);
|
||||
}, [transcriptId, !api]);
|
||||
|
||||
return {
|
||||
transcriptText,
|
||||
|
||||
Reference in New Issue
Block a user