mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
feat: complete migration from @hey-api/openapi-ts to openapi-react-query
- Migrated all components from useApi compatibility layer to direct React Query hooks - Added new hooks for participant operations, room meetings, and speaker operations - Updated all imports from old api module to api-types - Fixed TypeScript types and API endpoint signatures - Removed deprecated useApi.ts compatibility layer - Fixed SourceKind enum values to match OpenAPI spec - Added @ts-ignore for Zulip endpoints not in OpenAPI spec yet - Fixed all compilation errors and type issues
This commit is contained in:
@@ -10,6 +10,13 @@ interface FilterSidebarProps {
|
|||||||
onFilterChange: (sourceKind: SourceKind | null, roomId: string) => void;
|
onFilterChange: (sourceKind: SourceKind | null, roomId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type helper for source kind literals
|
||||||
|
const SK = {
|
||||||
|
room: "room" as SourceKind,
|
||||||
|
live: "live" as SourceKind,
|
||||||
|
file: "file" as SourceKind,
|
||||||
|
};
|
||||||
|
|
||||||
export default function FilterSidebar({
|
export default function FilterSidebar({
|
||||||
rooms,
|
rooms,
|
||||||
selectedSourceKind,
|
selectedSourceKind,
|
||||||
@@ -44,14 +51,14 @@ export default function FilterSidebar({
|
|||||||
key={room.id}
|
key={room.id}
|
||||||
as={NextLink}
|
as={NextLink}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={() => onFilterChange("room", room.id)}
|
onClick={() => onFilterChange(SK.room, room.id)}
|
||||||
color={
|
color={
|
||||||
selectedSourceKind === "room" && selectedRoomId === room.id
|
selectedSourceKind === SK.room && selectedRoomId === room.id
|
||||||
? "blue.500"
|
? "blue.500"
|
||||||
: "gray.600"
|
: "gray.600"
|
||||||
}
|
}
|
||||||
fontWeight={
|
fontWeight={
|
||||||
selectedSourceKind === "room" && selectedRoomId === room.id
|
selectedSourceKind === SK.room && selectedRoomId === room.id
|
||||||
? "bold"
|
? "bold"
|
||||||
: "normal"
|
: "normal"
|
||||||
}
|
}
|
||||||
@@ -72,14 +79,14 @@ export default function FilterSidebar({
|
|||||||
key={room.id}
|
key={room.id}
|
||||||
as={NextLink}
|
as={NextLink}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={() => onFilterChange("room", room.id)}
|
onClick={() => onFilterChange(SK.room, room.id)}
|
||||||
color={
|
color={
|
||||||
selectedSourceKind === "room" && selectedRoomId === room.id
|
selectedSourceKind === SK.room && selectedRoomId === room.id
|
||||||
? "blue.500"
|
? "blue.500"
|
||||||
: "gray.600"
|
: "gray.600"
|
||||||
}
|
}
|
||||||
fontWeight={
|
fontWeight={
|
||||||
selectedSourceKind === "room" && selectedRoomId === room.id
|
selectedSourceKind === SK.room && selectedRoomId === room.id
|
||||||
? "bold"
|
? "bold"
|
||||||
: "normal"
|
: "normal"
|
||||||
}
|
}
|
||||||
@@ -95,10 +102,10 @@ export default function FilterSidebar({
|
|||||||
<Link
|
<Link
|
||||||
as={NextLink}
|
as={NextLink}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={() => onFilterChange("live", "")}
|
onClick={() => onFilterChange(SK.live, "")}
|
||||||
color={selectedSourceKind === "live" ? "blue.500" : "gray.600"}
|
color={selectedSourceKind === SK.live ? "blue.500" : "gray.600"}
|
||||||
_hover={{ color: "blue.300" }}
|
_hover={{ color: "blue.300" }}
|
||||||
fontWeight={selectedSourceKind === "live" ? "bold" : "normal"}
|
fontWeight={selectedSourceKind === SK.live ? "bold" : "normal"}
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
>
|
>
|
||||||
Live Transcripts
|
Live Transcripts
|
||||||
@@ -106,10 +113,10 @@ export default function FilterSidebar({
|
|||||||
<Link
|
<Link
|
||||||
as={NextLink}
|
as={NextLink}
|
||||||
href="#"
|
href="#"
|
||||||
onClick={() => onFilterChange("file", "")}
|
onClick={() => onFilterChange(SK.file, "")}
|
||||||
color={selectedSourceKind === "file" ? "blue.500" : "gray.600"}
|
color={selectedSourceKind === SK.file ? "blue.500" : "gray.600"}
|
||||||
_hover={{ color: "blue.300" }}
|
_hover={{ color: "blue.300" }}
|
||||||
fontWeight={selectedSourceKind === "file" ? "bold" : "normal"}
|
fontWeight={selectedSourceKind === SK.file ? "bold" : "normal"}
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
>
|
>
|
||||||
Uploaded Files
|
Uploaded Files
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
highlightMatches,
|
highlightMatches,
|
||||||
generateTextFragment,
|
generateTextFragment,
|
||||||
} from "../../../lib/textHighlight";
|
} from "../../../lib/textHighlight";
|
||||||
import { SearchResult } from "../../../lib/api-types";
|
import { SearchResult, SourceKind } from "../../../lib/api-types";
|
||||||
|
|
||||||
interface TranscriptCardsProps {
|
interface TranscriptCardsProps {
|
||||||
results: SearchResult[];
|
results: SearchResult[];
|
||||||
@@ -120,7 +120,7 @@ function TranscriptCard({
|
|||||||
: "N/A";
|
: "N/A";
|
||||||
const formattedDate = formatLocalDate(result.created_at);
|
const formattedDate = formatLocalDate(result.created_at);
|
||||||
const source =
|
const source =
|
||||||
result.source_kind === "room"
|
result.source_kind === ("room" as SourceKind)
|
||||||
? result.room_name || result.room_id
|
? result.room_name || result.room_id
|
||||||
: result.source_kind;
|
: result.source_kind;
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export default function TranscriptBrowser() {
|
|||||||
|
|
||||||
const [urlSourceKind, setUrlSourceKind] = useQueryState(
|
const [urlSourceKind, setUrlSourceKind] = useQueryState(
|
||||||
"source",
|
"source",
|
||||||
parseAsStringLiteral($SourceKind.enum).withOptions({
|
parseAsStringLiteral($SourceKind.values).withOptions({
|
||||||
shallow: false,
|
shallow: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -302,7 +302,6 @@ export default function TranscriptBrowser() {
|
|||||||
params: {
|
params: {
|
||||||
path: { transcript_id: transcriptId },
|
path: { transcript_id: transcriptId },
|
||||||
},
|
},
|
||||||
body: {},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: (result) => {
|
onSuccess: (result) => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useRoomList from "./useRoomList";
|
import useRoomList from "./useRoomList";
|
||||||
import { ApiError, Room } from "../../lib/api-types";
|
import { Room } from "../../lib/api-types";
|
||||||
import {
|
import {
|
||||||
useRoomCreate,
|
useRoomCreate,
|
||||||
useRoomUpdate,
|
useRoomUpdate,
|
||||||
@@ -92,8 +92,10 @@ export default function RoomsList() {
|
|||||||
const createRoomMutation = useRoomCreate();
|
const createRoomMutation = useRoomCreate();
|
||||||
const updateRoomMutation = useRoomUpdate();
|
const updateRoomMutation = useRoomUpdate();
|
||||||
const deleteRoomMutation = useRoomDelete();
|
const deleteRoomMutation = useRoomDelete();
|
||||||
const { data: streams = [] } = useZulipStreams();
|
const { data: streams = [] } = useZulipStreams() as { data: any[] };
|
||||||
const { data: topics = [] } = useZulipTopics(selectedStreamId);
|
const { data: topics = [] } = useZulipTopics(selectedStreamId) as {
|
||||||
|
data: Topic[];
|
||||||
|
};
|
||||||
interface Topic {
|
interface Topic {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
@@ -177,11 +179,10 @@ export default function RoomsList() {
|
|||||||
setNameError("");
|
setNameError("");
|
||||||
refetch();
|
refetch();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
if (
|
if (
|
||||||
err instanceof ApiError &&
|
err?.status === 400 &&
|
||||||
err.status === 400 &&
|
err?.body?.detail == "Room name is not unique"
|
||||||
(err.body as any).detail == "Room name is not unique"
|
|
||||||
) {
|
) {
|
||||||
setNameError(
|
setNameError(
|
||||||
"This room name is already taken. Please choose a different name.",
|
"This room name is already taken. Please choose a different name.",
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import TopicPlayer from "./topicPlayer";
|
|||||||
import useParticipants from "../../useParticipants";
|
import useParticipants from "../../useParticipants";
|
||||||
import useTopicWithWords from "../../useTopicWithWords";
|
import useTopicWithWords from "../../useTopicWithWords";
|
||||||
import ParticipantList from "./participantList";
|
import ParticipantList from "./participantList";
|
||||||
import { GetTranscriptTopic } from "../../../../api";
|
import { GetTranscriptTopic } from "../../../../lib/api-types";
|
||||||
import { SelectedText, selectedTextIsTimeSlice } from "./types";
|
import { SelectedText, selectedTextIsTimeSlice } from "./types";
|
||||||
import useApi from "../../../../lib/useApi";
|
import { useTranscriptUpdate } from "../../../../lib/api-hooks";
|
||||||
import useTranscript from "../../useTranscript";
|
import useTranscript from "../../useTranscript";
|
||||||
import { useError } from "../../../../(errors)/errorContext";
|
import { useError } from "../../../../(errors)/errorContext";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
@@ -23,7 +23,7 @@ export type TranscriptCorrect = {
|
|||||||
export default function TranscriptCorrect({
|
export default function TranscriptCorrect({
|
||||||
params: { transcriptId },
|
params: { transcriptId },
|
||||||
}: TranscriptCorrect) {
|
}: TranscriptCorrect) {
|
||||||
const api = useApi();
|
const updateTranscriptMutation = useTranscriptUpdate();
|
||||||
const transcript = useTranscript(transcriptId);
|
const transcript = useTranscript(transcriptId);
|
||||||
const stateCurrentTopic = useState<GetTranscriptTopic>();
|
const stateCurrentTopic = useState<GetTranscriptTopic>();
|
||||||
const [currentTopic, _sct] = stateCurrentTopic;
|
const [currentTopic, _sct] = stateCurrentTopic;
|
||||||
@@ -34,16 +34,21 @@ export default function TranscriptCorrect({
|
|||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const markAsDone = () => {
|
const markAsDone = async () => {
|
||||||
if (transcript.response && !transcript.response.reviewed) {
|
if (transcript.response && !transcript.response.reviewed) {
|
||||||
api
|
try {
|
||||||
?.v1TranscriptUpdate({ transcriptId, requestBody: { reviewed: true } })
|
await updateTranscriptMutation.mutateAsync({
|
||||||
.then(() => {
|
params: {
|
||||||
router.push(`/transcripts/${transcriptId}`);
|
path: {
|
||||||
})
|
transcript_id: transcriptId,
|
||||||
.catch((e) => {
|
},
|
||||||
setError(e, "Error marking as done");
|
},
|
||||||
|
body: { reviewed: true },
|
||||||
});
|
});
|
||||||
|
router.push(`/transcripts/${transcriptId}`);
|
||||||
|
} catch (e) {
|
||||||
|
setError(e as Error, "Error marking as done");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import { faArrowTurnDown } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowTurnDown } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||||
import { Participant } from "../../../../api";
|
import { Participant } from "../../../../lib/api-types";
|
||||||
import useApi from "../../../../lib/useApi";
|
import {
|
||||||
|
useTranscriptSpeakerAssign,
|
||||||
|
useTranscriptSpeakerMerge,
|
||||||
|
useTranscriptParticipantUpdate,
|
||||||
|
useTranscriptParticipantCreate,
|
||||||
|
useTranscriptParticipantDelete,
|
||||||
|
} from "../../../../lib/api-hooks";
|
||||||
import { UseParticipants } from "../../useParticipants";
|
import { UseParticipants } from "../../useParticipants";
|
||||||
import { selectedTextIsSpeaker, selectedTextIsTimeSlice } from "./types";
|
import { selectedTextIsSpeaker, selectedTextIsTimeSlice } from "./types";
|
||||||
import { useError } from "../../../../(errors)/errorContext";
|
import { useError } from "../../../../(errors)/errorContext";
|
||||||
@@ -30,9 +36,19 @@ const ParticipantList = ({
|
|||||||
topicWithWords,
|
topicWithWords,
|
||||||
stateSelectedText,
|
stateSelectedText,
|
||||||
}: ParticipantList) => {
|
}: ParticipantList) => {
|
||||||
const api = useApi();
|
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
const [loading, setLoading] = useState(false);
|
const speakerAssignMutation = useTranscriptSpeakerAssign();
|
||||||
|
const speakerMergeMutation = useTranscriptSpeakerMerge();
|
||||||
|
const participantUpdateMutation = useTranscriptParticipantUpdate();
|
||||||
|
const participantCreateMutation = useTranscriptParticipantCreate();
|
||||||
|
const participantDeleteMutation = useTranscriptParticipantDelete();
|
||||||
|
|
||||||
|
const loading =
|
||||||
|
speakerAssignMutation.isPending ||
|
||||||
|
speakerMergeMutation.isPending ||
|
||||||
|
participantUpdateMutation.isPending ||
|
||||||
|
participantCreateMutation.isPending ||
|
||||||
|
participantDeleteMutation.isPending;
|
||||||
const [participantInput, setParticipantInput] = useState("");
|
const [participantInput, setParticipantInput] = useState("");
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [selectedText, setSelectedText] = stateSelectedText;
|
const [selectedText, setSelectedText] = stateSelectedText;
|
||||||
@@ -103,7 +119,6 @@ const ParticipantList = ({
|
|||||||
const onSuccess = () => {
|
const onSuccess = () => {
|
||||||
topicWithWords.refetch();
|
topicWithWords.refetch();
|
||||||
participants.refetch();
|
participants.refetch();
|
||||||
setLoading(false);
|
|
||||||
setAction(null);
|
setAction(null);
|
||||||
setSelectedText(undefined);
|
setSelectedText(undefined);
|
||||||
setSelectedParticipant(undefined);
|
setSelectedParticipant(undefined);
|
||||||
@@ -120,11 +135,14 @@ const ParticipantList = ({
|
|||||||
if (loading || participants.loading || topicWithWords.loading) return;
|
if (loading || participants.loading || topicWithWords.loading) return;
|
||||||
if (!selectedTextIsTimeSlice(selectedText)) return;
|
if (!selectedTextIsTimeSlice(selectedText)) return;
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
try {
|
||||||
await api?.v1TranscriptAssignSpeaker({
|
await speakerAssignMutation.mutateAsync({
|
||||||
transcriptId,
|
params: {
|
||||||
requestBody: {
|
path: {
|
||||||
|
transcript_id: transcriptId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
participant: participant.id,
|
participant: participant.id,
|
||||||
timestamp_from: selectedText.start,
|
timestamp_from: selectedText.start,
|
||||||
timestamp_to: selectedText.end,
|
timestamp_to: selectedText.end,
|
||||||
@@ -132,8 +150,7 @@ const ParticipantList = ({
|
|||||||
});
|
});
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error, "There was an error assigning");
|
setError(error as Error, "There was an error assigning");
|
||||||
setLoading(false);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -141,32 +158,38 @@ const ParticipantList = ({
|
|||||||
const mergeSpeaker =
|
const mergeSpeaker =
|
||||||
(speakerFrom, participantTo: Participant) => async () => {
|
(speakerFrom, participantTo: Participant) => async () => {
|
||||||
if (loading || participants.loading || topicWithWords.loading) return;
|
if (loading || participants.loading || topicWithWords.loading) return;
|
||||||
setLoading(true);
|
|
||||||
if (participantTo.speaker) {
|
if (participantTo.speaker) {
|
||||||
try {
|
try {
|
||||||
await api?.v1TranscriptMergeSpeaker({
|
await speakerMergeMutation.mutateAsync({
|
||||||
transcriptId,
|
params: {
|
||||||
requestBody: {
|
path: {
|
||||||
|
transcript_id: transcriptId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
speaker_from: speakerFrom,
|
speaker_from: speakerFrom,
|
||||||
speaker_to: participantTo.speaker,
|
speaker_to: participantTo.speaker,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error, "There was an error merging");
|
setError(error as Error, "There was an error merging");
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await api?.v1TranscriptUpdateParticipant({
|
await participantUpdateMutation.mutateAsync({
|
||||||
transcriptId,
|
params: {
|
||||||
participantId: participantTo.id,
|
path: {
|
||||||
requestBody: { speaker: speakerFrom },
|
transcript_id: transcriptId,
|
||||||
|
participant_id: participantTo.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: { speaker: speakerFrom },
|
||||||
});
|
});
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error, "There was an error merging (update)");
|
setError(error as Error, "There was an error merging (update)");
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -186,105 +209,106 @@ const ParticipantList = ({
|
|||||||
(p) => p.speaker == selectedText,
|
(p) => p.speaker == selectedText,
|
||||||
);
|
);
|
||||||
if (participant && participant.name !== participantInput) {
|
if (participant && participant.name !== participantInput) {
|
||||||
setLoading(true);
|
try {
|
||||||
api
|
await participantUpdateMutation.mutateAsync({
|
||||||
?.v1TranscriptUpdateParticipant({
|
params: {
|
||||||
transcriptId,
|
path: {
|
||||||
participantId: participant.id,
|
transcript_id: transcriptId,
|
||||||
requestBody: {
|
participant_id: participant.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
name: participantInput,
|
name: participantInput,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
participants.refetch();
|
|
||||||
setLoading(false);
|
|
||||||
setAction(null);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
setError(e, "There was an error renaming");
|
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
|
participants.refetch();
|
||||||
|
setAction(null);
|
||||||
|
} catch (e) {
|
||||||
|
setError(e as Error, "There was an error renaming");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
action == "Create to rename" &&
|
action == "Create to rename" &&
|
||||||
selectedTextIsSpeaker(selectedText)
|
selectedTextIsSpeaker(selectedText)
|
||||||
) {
|
) {
|
||||||
setLoading(true);
|
try {
|
||||||
api
|
await participantCreateMutation.mutateAsync({
|
||||||
?.v1TranscriptAddParticipant({
|
params: {
|
||||||
transcriptId,
|
path: {
|
||||||
requestBody: {
|
transcript_id: transcriptId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
name: participantInput,
|
name: participantInput,
|
||||||
speaker: selectedText,
|
speaker: selectedText,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
participants.refetch();
|
|
||||||
setParticipantInput("");
|
|
||||||
setOneMatch(undefined);
|
|
||||||
setLoading(false);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
setError(e, "There was an error creating");
|
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
|
participants.refetch();
|
||||||
|
setParticipantInput("");
|
||||||
|
setOneMatch(undefined);
|
||||||
|
} catch (e) {
|
||||||
|
setError(e as Error, "There was an error creating");
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
action == "Create and assign" &&
|
action == "Create and assign" &&
|
||||||
selectedTextIsTimeSlice(selectedText)
|
selectedTextIsTimeSlice(selectedText)
|
||||||
) {
|
) {
|
||||||
setLoading(true);
|
|
||||||
try {
|
try {
|
||||||
const participant = await api?.v1TranscriptAddParticipant({
|
const participant = await participantCreateMutation.mutateAsync({
|
||||||
transcriptId,
|
params: {
|
||||||
requestBody: {
|
path: {
|
||||||
|
transcript_id: transcriptId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
name: participantInput,
|
name: participantInput,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
setLoading(false);
|
|
||||||
assignTo(participant)().catch(() => {
|
assignTo(participant)().catch(() => {
|
||||||
// error and loading are handled by assignTo catch
|
// error and loading are handled by assignTo catch
|
||||||
participants.refetch();
|
participants.refetch();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(e, "There was an error creating");
|
setError(error as Error, "There was an error creating");
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
} else if (action == "Create") {
|
} else if (action == "Create") {
|
||||||
setLoading(true);
|
try {
|
||||||
api
|
await participantCreateMutation.mutateAsync({
|
||||||
?.v1TranscriptAddParticipant({
|
params: {
|
||||||
transcriptId,
|
path: {
|
||||||
requestBody: {
|
transcript_id: transcriptId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
name: participantInput,
|
name: participantInput,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
participants.refetch();
|
|
||||||
setParticipantInput("");
|
|
||||||
setLoading(false);
|
|
||||||
inputRef.current?.focus();
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
setError(e, "There was an error creating");
|
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
|
participants.refetch();
|
||||||
|
setParticipantInput("");
|
||||||
|
inputRef.current?.focus();
|
||||||
|
} catch (e) {
|
||||||
|
setError(e as Error, "There was an error creating");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteParticipant = (participantId) => (e) => {
|
const deleteParticipant = (participantId) => async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (loading || participants.loading || topicWithWords.loading) return;
|
if (loading || participants.loading || topicWithWords.loading) return;
|
||||||
setLoading(true);
|
|
||||||
api
|
try {
|
||||||
?.v1TranscriptDeleteParticipant({ transcriptId, participantId })
|
await participantDeleteMutation.mutateAsync({
|
||||||
.then(() => {
|
params: {
|
||||||
participants.refetch();
|
path: {
|
||||||
setLoading(false);
|
transcript_id: transcriptId,
|
||||||
})
|
participant_id: participantId,
|
||||||
.catch((e) => {
|
},
|
||||||
setError(e, "There was an error deleting");
|
},
|
||||||
setLoading(false);
|
|
||||||
});
|
});
|
||||||
|
participants.refetch();
|
||||||
|
} catch (e) {
|
||||||
|
setError(e as Error, "There was an error deleting");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectParticipant = (participant) => (e) => {
|
const selectParticipant = (participant) => (e) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import useTopics from "../../useTopics";
|
import useTopics from "../../useTopics";
|
||||||
import { Dispatch, SetStateAction, useEffect } from "react";
|
import { Dispatch, SetStateAction, useEffect } from "react";
|
||||||
import { GetTranscriptTopic } from "../../../../api";
|
import { GetTranscriptTopic } from "../../../../lib/api-types";
|
||||||
import {
|
import {
|
||||||
BoxProps,
|
BoxProps,
|
||||||
Box,
|
Box,
|
||||||
|
|||||||
@@ -2,12 +2,8 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
import "../../../styles/markdown.css";
|
import "../../../styles/markdown.css";
|
||||||
import {
|
import { GetTranscript, GetTranscriptTopic } from "../../../lib/api-types";
|
||||||
GetTranscript,
|
import { useTranscriptUpdate } from "../../../lib/api-hooks";
|
||||||
GetTranscriptTopic,
|
|
||||||
UpdateTranscript,
|
|
||||||
} from "../../../lib/api-types";
|
|
||||||
import useApi from "../../../lib/useApi";
|
|
||||||
import {
|
import {
|
||||||
Flex,
|
Flex,
|
||||||
Heading,
|
Heading,
|
||||||
@@ -33,9 +29,8 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
|||||||
const [preEditSummary, setPreEditSummary] = useState("");
|
const [preEditSummary, setPreEditSummary] = useState("");
|
||||||
const [editedSummary, setEditedSummary] = useState("");
|
const [editedSummary, setEditedSummary] = useState("");
|
||||||
|
|
||||||
const api = useApi();
|
|
||||||
|
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
|
const updateTranscriptMutation = useTranscriptUpdate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEditedSummary(props.transcriptResponse?.long_summary || "");
|
setEditedSummary(props.transcriptResponse?.long_summary || "");
|
||||||
@@ -47,12 +42,15 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
|||||||
|
|
||||||
const updateSummary = async (newSummary: string, transcriptId: string) => {
|
const updateSummary = async (newSummary: string, transcriptId: string) => {
|
||||||
try {
|
try {
|
||||||
const requestBody: UpdateTranscript = {
|
const updatedTranscript = await updateTranscriptMutation.mutateAsync({
|
||||||
long_summary: newSummary,
|
params: {
|
||||||
};
|
path: {
|
||||||
const updatedTranscript = await api?.v1TranscriptUpdate({
|
transcript_id: transcriptId,
|
||||||
transcriptId,
|
},
|
||||||
requestBody,
|
},
|
||||||
|
body: {
|
||||||
|
long_summary: newSummary,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (props.onUpdate) {
|
if (props.onUpdate) {
|
||||||
props.onUpdate(newSummary);
|
props.onUpdate(newSummary);
|
||||||
@@ -60,7 +58,7 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
|||||||
console.log("Updated long summary:", updatedTranscript);
|
console.log("Updated long summary:", updatedTranscript);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to update long summary:", err);
|
console.error("Failed to update long summary:", err);
|
||||||
setError(err, "Failed to update long summary.");
|
setError(err as Error, "Failed to update long summary.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,7 +112,12 @@ export default function FinalSummary(props: FinalSummaryProps) {
|
|||||||
<Button onClick={onDiscardClick} variant="ghost">
|
<Button onClick={onDiscardClick} variant="ghost">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onSaveClick}>Save</Button>
|
<Button
|
||||||
|
onClick={onSaveClick}
|
||||||
|
disabled={updateTranscriptMutation.isPending}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
{!isEditMode && (
|
{!isEditMode && (
|
||||||
|
|||||||
@@ -1,45 +1,30 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
import { useError } from "../../(errors)/errorContext";
|
|
||||||
import { CreateTranscript, GetTranscript } from "../../lib/api-types";
|
import { CreateTranscript, GetTranscript } from "../../lib/api-types";
|
||||||
import useApi from "../../lib/useApi";
|
import { useTranscriptCreate } from "../../lib/api-hooks";
|
||||||
|
|
||||||
type UseCreateTranscript = {
|
type UseCreateTranscript = {
|
||||||
transcript: GetTranscript | null;
|
transcript: GetTranscript | null;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
create: (transcriptCreationDetails: CreateTranscript) => void;
|
create: (transcriptCreationDetails: CreateTranscript) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useCreateTranscript = (): UseCreateTranscript => {
|
const useCreateTranscript = (): UseCreateTranscript => {
|
||||||
const [transcript, setTranscript] = useState<GetTranscript | null>(null);
|
const createMutation = useTranscriptCreate();
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
const [error, setErrorState] = useState<Error | null>(null);
|
|
||||||
const { setError } = useError();
|
|
||||||
const api = useApi();
|
|
||||||
|
|
||||||
const create = (transcriptCreationDetails: CreateTranscript) => {
|
const create = async (transcriptCreationDetails: CreateTranscript) => {
|
||||||
if (loading || !api) return;
|
if (createMutation.isPending) return;
|
||||||
|
|
||||||
setLoading(true);
|
await createMutation.mutateAsync({
|
||||||
|
body: transcriptCreationDetails,
|
||||||
api
|
});
|
||||||
.v1TranscriptsCreate({ requestBody: transcriptCreationDetails })
|
|
||||||
.then((transcript) => {
|
|
||||||
setTranscript(transcript);
|
|
||||||
setLoading(false);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setError(
|
|
||||||
err,
|
|
||||||
"There was an issue creating a transcript, please try again.",
|
|
||||||
);
|
|
||||||
setErrorState(err);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return { transcript, loading, error, create };
|
return {
|
||||||
|
transcript: createMutation.data || null,
|
||||||
|
loading: createMutation.isPending,
|
||||||
|
error: createMutation.error as Error | null,
|
||||||
|
create,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useCreateTranscript;
|
export default useCreateTranscript;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import useApi from "../../lib/useApi";
|
import { useTranscriptUploadAudio } from "../../lib/api-hooks";
|
||||||
import { Button, Spinner } from "@chakra-ui/react";
|
import { Button, Spinner } from "@chakra-ui/react";
|
||||||
|
import { useError } from "../../(errors)/errorContext";
|
||||||
|
|
||||||
type FileUploadButton = {
|
type FileUploadButton = {
|
||||||
transcriptId: string;
|
transcriptId: string;
|
||||||
@@ -8,13 +9,16 @@ type FileUploadButton = {
|
|||||||
|
|
||||||
export default function FileUploadButton(props: FileUploadButton) {
|
export default function FileUploadButton(props: FileUploadButton) {
|
||||||
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
const api = useApi();
|
const uploadMutation = useTranscriptUploadAudio();
|
||||||
|
const { setError } = useError();
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
const triggerFileUpload = () => {
|
const triggerFileUpload = () => {
|
||||||
fileInputRef.current?.click();
|
fileInputRef.current?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileUpload = async (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
) => {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
@@ -24,37 +28,45 @@ export default function FileUploadButton(props: FileUploadButton) {
|
|||||||
let start = 0;
|
let start = 0;
|
||||||
let uploadedSize = 0;
|
let uploadedSize = 0;
|
||||||
|
|
||||||
api?.httpRequest.config.interceptors.request.use((request) => {
|
|
||||||
request.onUploadProgress = (progressEvent) => {
|
|
||||||
const currentProgress = Math.floor(
|
|
||||||
((uploadedSize + progressEvent.loaded) / file.size) * 100,
|
|
||||||
);
|
|
||||||
setProgress(currentProgress);
|
|
||||||
};
|
|
||||||
return request;
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploadNextChunk = async () => {
|
const uploadNextChunk = async () => {
|
||||||
if (chunkNumber == totalChunks) return;
|
if (chunkNumber == totalChunks) {
|
||||||
|
setProgress(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chunkSize = Math.min(maxChunkSize, file.size - start);
|
const chunkSize = Math.min(maxChunkSize, file.size - start);
|
||||||
const end = start + chunkSize;
|
const end = start + chunkSize;
|
||||||
const chunk = file.slice(start, end);
|
const chunk = file.slice(start, end);
|
||||||
|
|
||||||
await api?.v1TranscriptRecordUpload({
|
try {
|
||||||
transcriptId: props.transcriptId,
|
const formData = new FormData();
|
||||||
formData: {
|
formData.append("chunk", chunk);
|
||||||
chunk,
|
|
||||||
},
|
|
||||||
chunkNumber,
|
|
||||||
totalChunks,
|
|
||||||
});
|
|
||||||
|
|
||||||
uploadedSize += chunkSize;
|
await uploadMutation.mutateAsync({
|
||||||
chunkNumber++;
|
params: {
|
||||||
start = end;
|
path: {
|
||||||
|
transcript_id: props.transcriptId,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
chunk_number: chunkNumber,
|
||||||
|
total_chunks: totalChunks,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: formData as any,
|
||||||
|
});
|
||||||
|
|
||||||
uploadNextChunk();
|
uploadedSize += chunkSize;
|
||||||
|
const currentProgress = Math.floor((uploadedSize / file.size) * 100);
|
||||||
|
setProgress(currentProgress);
|
||||||
|
|
||||||
|
chunkNumber++;
|
||||||
|
start = end;
|
||||||
|
|
||||||
|
await uploadNextChunk();
|
||||||
|
} catch (error) {
|
||||||
|
setError(error as Error, "Failed to upload file");
|
||||||
|
setProgress(0);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
uploadNextChunk();
|
uploadNextChunk();
|
||||||
|
|||||||
@@ -54,20 +54,30 @@ const TranscriptCreate = () => {
|
|||||||
const [loadingUpload, setLoadingUpload] = useState(false);
|
const [loadingUpload, setLoadingUpload] = useState(false);
|
||||||
|
|
||||||
const getTargetLanguage = () => {
|
const getTargetLanguage = () => {
|
||||||
if (targetLanguage === "NOTRANSLATION") return;
|
if (targetLanguage === "NOTRANSLATION") return undefined;
|
||||||
return targetLanguage;
|
return targetLanguage;
|
||||||
};
|
};
|
||||||
|
|
||||||
const send = () => {
|
const send = () => {
|
||||||
if (loadingRecord || createTranscript.loading || permissionDenied) return;
|
if (loadingRecord || createTranscript.loading || permissionDenied) return;
|
||||||
setLoadingRecord(true);
|
setLoadingRecord(true);
|
||||||
createTranscript.create({ name, target_language: getTargetLanguage() });
|
const targetLang = getTargetLanguage();
|
||||||
|
createTranscript.create({
|
||||||
|
name,
|
||||||
|
source_language: "en",
|
||||||
|
target_language: targetLang || "en",
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadFile = () => {
|
const uploadFile = () => {
|
||||||
if (loadingUpload || createTranscript.loading || permissionDenied) return;
|
if (loadingUpload || createTranscript.loading || permissionDenied) return;
|
||||||
setLoadingUpload(true);
|
setLoadingUpload(true);
|
||||||
createTranscript.create({ name, target_language: getTargetLanguage() });
|
const targetLang = getTargetLanguage();
|
||||||
|
createTranscript.create({
|
||||||
|
name,
|
||||||
|
source_language: "en",
|
||||||
|
target_language: targetLang || "en",
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
|||||||
"This transcript is public. Everyone can access it."}
|
"This transcript is public. Everyone can access it."}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{isOwner && api && (
|
{isOwner && (
|
||||||
<Select.Root
|
<Select.Root
|
||||||
key={shareMode.value}
|
key={shareMode.value}
|
||||||
value={[shareMode.value || ""]}
|
value={[shareMode.value || ""]}
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ import {
|
|||||||
useListCollection,
|
useListCollection,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { TbBrandZulip } from "react-icons/tb";
|
import { TbBrandZulip } from "react-icons/tb";
|
||||||
import useApi from "../../lib/useApi";
|
import {
|
||||||
|
useZulipStreams,
|
||||||
|
useZulipTopics,
|
||||||
|
useTranscriptPostToZulip,
|
||||||
|
} from "../../lib/api-hooks";
|
||||||
|
|
||||||
type ShareZulipProps = {
|
type ShareZulipProps = {
|
||||||
transcriptResponse: GetTranscript;
|
transcriptResponse: GetTranscript;
|
||||||
@@ -37,97 +41,76 @@ interface Topic {
|
|||||||
export default function ShareZulip(props: ShareZulipProps & BoxProps) {
|
export default function ShareZulip(props: ShareZulipProps & BoxProps) {
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [stream, setStream] = useState<string | undefined>(undefined);
|
const [stream, setStream] = useState<string | undefined>(undefined);
|
||||||
|
const [selectedStreamId, setSelectedStreamId] = useState<number | null>(null);
|
||||||
const [topic, setTopic] = useState<string | undefined>(undefined);
|
const [topic, setTopic] = useState<string | undefined>(undefined);
|
||||||
const [includeTopics, setIncludeTopics] = useState(false);
|
const [includeTopics, setIncludeTopics] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [streams, setStreams] = useState<Stream[]>([]);
|
// React Query hooks
|
||||||
const [topics, setTopics] = useState<Topic[]>([]);
|
const { data: streams = [], isLoading: isLoadingStreams } =
|
||||||
const api = useApi();
|
useZulipStreams() as { data: Stream[]; isLoading: boolean };
|
||||||
|
const { data: topics = [] } = useZulipTopics(selectedStreamId) as {
|
||||||
|
data: Topic[];
|
||||||
|
};
|
||||||
|
const postToZulipMutation = useTranscriptPostToZulip();
|
||||||
|
|
||||||
const { contains } = useFilter({ sensitivity: "base" });
|
const { contains } = useFilter({ sensitivity: "base" });
|
||||||
|
|
||||||
const {
|
const streamItems = useMemo(() => {
|
||||||
collection: streamItemsCollection,
|
return (streams || []).map((stream: Stream) => ({
|
||||||
filter: streamItemsFilter,
|
label: stream.name,
|
||||||
set: streamItemsSet,
|
value: stream.name,
|
||||||
} = useListCollection({
|
}));
|
||||||
initialItems: [] as { label: string; value: string }[],
|
}, [streams]);
|
||||||
filter: contains,
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
const topicItems = useMemo(() => {
|
||||||
collection: topicItemsCollection,
|
return (topics || []).map((topic: Topic) => ({
|
||||||
filter: topicItemsFilter,
|
label: topic.name,
|
||||||
set: topicItemsSet,
|
value: topic.name,
|
||||||
} = useListCollection({
|
}));
|
||||||
initialItems: [] as { label: string; value: string }[],
|
}, [topics]);
|
||||||
filter: contains,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const { collection: streamItemsCollection, filter: streamItemsFilter } =
|
||||||
|
useListCollection({
|
||||||
|
initialItems: streamItems,
|
||||||
|
filter: contains,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { collection: topicItemsCollection, filter: topicItemsFilter } =
|
||||||
|
useListCollection({
|
||||||
|
initialItems: topicItems,
|
||||||
|
filter: contains,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update selected stream ID when stream changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchZulipStreams = async () => {
|
if (stream && streams) {
|
||||||
if (!api) return;
|
const selectedStream = streams.find((s: Stream) => s.name === stream);
|
||||||
|
setSelectedStreamId(selectedStream ? selectedStream.stream_id : null);
|
||||||
try {
|
} else {
|
||||||
const response = await api.v1ZulipGetStreams();
|
setSelectedStreamId(null);
|
||||||
setStreams(response);
|
}
|
||||||
|
}, [stream, streams]);
|
||||||
streamItemsSet(
|
|
||||||
response.map((stream) => ({
|
|
||||||
label: stream.name,
|
|
||||||
value: stream.name,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching Zulip streams:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchZulipStreams();
|
|
||||||
}, [!api]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchZulipTopics = async () => {
|
|
||||||
if (!api || !stream) return;
|
|
||||||
try {
|
|
||||||
const selectedStream = streams.find((s) => s.name === stream);
|
|
||||||
if (selectedStream) {
|
|
||||||
const response = await api.v1ZulipGetTopics({
|
|
||||||
streamId: selectedStream.stream_id,
|
|
||||||
});
|
|
||||||
setTopics(response);
|
|
||||||
topicItemsSet(
|
|
||||||
response.map((topic) => ({
|
|
||||||
label: topic.name,
|
|
||||||
value: topic.name,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
topicItemsSet([]);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching Zulip topics:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchZulipTopics();
|
|
||||||
}, [stream, streams, api]);
|
|
||||||
|
|
||||||
const handleSendToZulip = async () => {
|
const handleSendToZulip = async () => {
|
||||||
if (!api || !props.transcriptResponse) return;
|
if (!props.transcriptResponse) return;
|
||||||
|
|
||||||
if (stream && topic) {
|
if (stream && topic) {
|
||||||
try {
|
try {
|
||||||
await api.v1TranscriptPostToZulip({
|
await postToZulipMutation.mutateAsync({
|
||||||
transcriptId: props.transcriptResponse.id,
|
params: {
|
||||||
stream,
|
path: {
|
||||||
topic,
|
transcript_id: props.transcriptResponse.id,
|
||||||
includeTopics,
|
},
|
||||||
|
query: {
|
||||||
|
stream,
|
||||||
|
topic,
|
||||||
|
include_topics: includeTopics,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error("Error posting to Zulip:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -155,7 +138,7 @@ export default function ShareZulip(props: ShareZulipProps & BoxProps) {
|
|||||||
</Dialog.CloseTrigger>
|
</Dialog.CloseTrigger>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
<Dialog.Body>
|
<Dialog.Body>
|
||||||
{isLoading ? (
|
{isLoadingStreams ? (
|
||||||
<Flex justify="center" py={8}>
|
<Flex justify="center" py={8}>
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { DomainContext } from "../../domainContext";
|
import { DomainContext } from "../../domainContext";
|
||||||
import getApi from "../../lib/useApi";
|
import { useTranscriptGet } from "../../lib/api-hooks";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
export type Mp3Response = {
|
export type Mp3Response = {
|
||||||
media: HTMLMediaElement | null;
|
media: HTMLMediaElement | null;
|
||||||
@@ -17,14 +18,17 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
|
|||||||
const [audioLoadingError, setAudioLoadingError] = useState<null | string>(
|
const [audioLoadingError, setAudioLoadingError] = useState<null | string>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [transcriptMetadataLoading, setTranscriptMetadataLoading] =
|
|
||||||
useState<boolean>(true);
|
|
||||||
const [transcriptMetadataLoadingError, setTranscriptMetadataLoadingError] =
|
|
||||||
useState<string | null>(null);
|
|
||||||
const [audioDeleted, setAudioDeleted] = useState<boolean | null>(null);
|
const [audioDeleted, setAudioDeleted] = useState<boolean | null>(null);
|
||||||
const api = getApi();
|
|
||||||
const { api_url } = useContext(DomainContext);
|
const { api_url } = useContext(DomainContext);
|
||||||
const accessTokenInfo = api?.httpRequest?.config?.TOKEN;
|
const { data: session } = useSession();
|
||||||
|
const accessTokenInfo = (session as any)?.accessToken as string | undefined;
|
||||||
|
|
||||||
|
// Use React Query to fetch transcript metadata
|
||||||
|
const {
|
||||||
|
data: transcript,
|
||||||
|
isLoading: transcriptMetadataLoading,
|
||||||
|
error: transcriptError,
|
||||||
|
} = useTranscriptGet(later ? null : transcriptId);
|
||||||
|
|
||||||
const [serviceWorker, setServiceWorker] =
|
const [serviceWorker, setServiceWorker] =
|
||||||
useState<ServiceWorkerRegistration | null>(null);
|
useState<ServiceWorkerRegistration | null>(null);
|
||||||
@@ -52,72 +56,50 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
|
|||||||
}, [navigator.serviceWorker, !serviceWorker, accessTokenInfo]);
|
}, [navigator.serviceWorker, !serviceWorker, accessTokenInfo]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!transcriptId || !api || later) return;
|
if (!transcriptId || later || !transcript) return;
|
||||||
|
|
||||||
let stopped = false;
|
let stopped = false;
|
||||||
let audioElement: HTMLAudioElement | null = null;
|
let audioElement: HTMLAudioElement | null = null;
|
||||||
let handleCanPlay: (() => void) | null = null;
|
let handleCanPlay: (() => void) | null = null;
|
||||||
let handleError: (() => void) | null = null;
|
let handleError: (() => void) | null = null;
|
||||||
|
|
||||||
setTranscriptMetadataLoading(true);
|
|
||||||
setAudioLoading(true);
|
setAudioLoading(true);
|
||||||
|
|
||||||
// First fetch transcript info to check if audio is deleted
|
const deleted = transcript.audio_deleted || false;
|
||||||
api
|
setAudioDeleted(deleted);
|
||||||
.v1TranscriptGet({ transcriptId })
|
|
||||||
.then((transcript) => {
|
|
||||||
if (stopped) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleted = transcript.audio_deleted || false;
|
if (deleted) {
|
||||||
setAudioDeleted(deleted);
|
// Audio is deleted, don't attempt to load it
|
||||||
setTranscriptMetadataLoadingError(null);
|
setMedia(null);
|
||||||
|
setAudioLoadingError(null);
|
||||||
|
setAudioLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (deleted) {
|
// Audio is not deleted, proceed to load it
|
||||||
// Audio is deleted, don't attempt to load it
|
audioElement = document.createElement("audio");
|
||||||
setMedia(null);
|
audioElement.src = `${api_url}/v1/transcripts/${transcriptId}/audio/mp3`;
|
||||||
setAudioLoadingError(null);
|
audioElement.crossOrigin = "anonymous";
|
||||||
setAudioLoading(false);
|
audioElement.preload = "auto";
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audio is not deleted, proceed to load it
|
handleCanPlay = () => {
|
||||||
audioElement = document.createElement("audio");
|
if (stopped) return;
|
||||||
audioElement.src = `${api_url}/v1/transcripts/${transcriptId}/audio/mp3`;
|
setAudioLoading(false);
|
||||||
audioElement.crossOrigin = "anonymous";
|
setAudioLoadingError(null);
|
||||||
audioElement.preload = "auto";
|
};
|
||||||
|
|
||||||
handleCanPlay = () => {
|
handleError = () => {
|
||||||
if (stopped) return;
|
if (stopped) return;
|
||||||
setAudioLoading(false);
|
setAudioLoading(false);
|
||||||
setAudioLoadingError(null);
|
setAudioLoadingError("Failed to load audio");
|
||||||
};
|
};
|
||||||
|
|
||||||
handleError = () => {
|
audioElement.addEventListener("canplay", handleCanPlay);
|
||||||
if (stopped) return;
|
audioElement.addEventListener("error", handleError);
|
||||||
setAudioLoading(false);
|
|
||||||
setAudioLoadingError("Failed to load audio");
|
|
||||||
};
|
|
||||||
|
|
||||||
audioElement.addEventListener("canplay", handleCanPlay);
|
if (!stopped) {
|
||||||
audioElement.addEventListener("error", handleError);
|
setMedia(audioElement);
|
||||||
|
}
|
||||||
if (!stopped) {
|
|
||||||
setMedia(audioElement);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
if (stopped) return;
|
|
||||||
console.error("Failed to fetch transcript:", error);
|
|
||||||
setAudioDeleted(null);
|
|
||||||
setTranscriptMetadataLoadingError(error.message);
|
|
||||||
setAudioLoading(false);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
if (stopped) return;
|
|
||||||
setTranscriptMetadataLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
stopped = true;
|
stopped = true;
|
||||||
@@ -128,14 +110,18 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
|
|||||||
if (handleError) audioElement.removeEventListener("error", handleError);
|
if (handleError) audioElement.removeEventListener("error", handleError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [transcriptId, api, later, api_url]);
|
}, [transcriptId, transcript, later, api_url]);
|
||||||
|
|
||||||
const getNow = () => {
|
const getNow = () => {
|
||||||
setLater(false);
|
setLater(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loading = audioLoading || transcriptMetadataLoading;
|
const loading = audioLoading || transcriptMetadataLoading;
|
||||||
const error = audioLoadingError || transcriptMetadataLoadingError;
|
const error =
|
||||||
|
audioLoadingError ||
|
||||||
|
(transcriptError
|
||||||
|
? (transcriptError as any).message || String(transcriptError)
|
||||||
|
: null);
|
||||||
|
|
||||||
return { media, loading, error, getNow, audioDeleted };
|
return { media, loading, error, getNow, audioDeleted };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Participant } from "../../lib/api-types";
|
import { Participant } from "../../lib/api-types";
|
||||||
import { useError } from "../../(errors)/errorContext";
|
import { useTranscriptParticipants } from "../../lib/api-hooks";
|
||||||
import useApi from "../../lib/useApi";
|
|
||||||
import { shouldShowError } from "../../lib/errorUtils";
|
|
||||||
|
|
||||||
type ErrorParticipants = {
|
type ErrorParticipants = {
|
||||||
error: Error;
|
error: Error;
|
||||||
@@ -29,46 +26,38 @@ export type UseParticipants = (
|
|||||||
) & { refetch: () => void };
|
) & { refetch: () => void };
|
||||||
|
|
||||||
const useParticipants = (transcriptId: string): UseParticipants => {
|
const useParticipants = (transcriptId: string): UseParticipants => {
|
||||||
const [response, setResponse] = useState<Participant[] | null>(null);
|
const {
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
data: response,
|
||||||
const [error, setErrorState] = useState<Error | null>(null);
|
isLoading: loading,
|
||||||
const { setError } = useError();
|
error,
|
||||||
const api = useApi();
|
refetch,
|
||||||
const [count, setCount] = useState(0);
|
} = useTranscriptParticipants(transcriptId || null);
|
||||||
|
|
||||||
const refetch = () => {
|
// Type-safe return based on state
|
||||||
if (!loading) {
|
if (error) {
|
||||||
setCount(count + 1);
|
return {
|
||||||
setLoading(true);
|
error: error as Error,
|
||||||
setErrorState(null);
|
loading: false,
|
||||||
}
|
response: null,
|
||||||
};
|
refetch,
|
||||||
|
} as ErrorParticipants & { refetch: () => void };
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
if (loading || !response) {
|
||||||
if (!transcriptId || !api) return;
|
return {
|
||||||
|
response: response || null,
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
refetch,
|
||||||
|
} as LoadingParticipants & { refetch: () => void };
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
return {
|
||||||
api
|
response,
|
||||||
.v1TranscriptGetParticipants({ transcriptId })
|
loading: false,
|
||||||
.then((result) => {
|
error: null,
|
||||||
setResponse(result);
|
refetch,
|
||||||
setLoading(false);
|
} as SuccessParticipants & { refetch: () => void };
|
||||||
console.debug("Participants Loaded:", result);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
const shouldShowHuman = shouldShowError(error);
|
|
||||||
if (shouldShowHuman) {
|
|
||||||
setError(error, "There was an error loading the participants");
|
|
||||||
} else {
|
|
||||||
setError(error);
|
|
||||||
}
|
|
||||||
setErrorState(error);
|
|
||||||
setResponse(null);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}, [transcriptId, !api, count]);
|
|
||||||
|
|
||||||
return { response, loading, error, refetch } as UseParticipants;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useParticipants;
|
export default useParticipants;
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
import { GetTranscriptTopicWithWordsPerSpeaker } from "../../lib/api-types";
|
import { GetTranscriptTopicWithWordsPerSpeaker } from "../../lib/api-types";
|
||||||
import { useError } from "../../(errors)/errorContext";
|
import { useTranscriptTopicsWithWordsPerSpeaker } from "../../lib/api-hooks";
|
||||||
import useApi from "../../lib/useApi";
|
|
||||||
import { shouldShowError } from "../../lib/errorUtils";
|
|
||||||
|
|
||||||
type ErrorTopicWithWords = {
|
type ErrorTopicWithWords = {
|
||||||
error: Error;
|
error: Error;
|
||||||
@@ -33,47 +29,41 @@ const useTopicWithWords = (
|
|||||||
topicId: string | undefined,
|
topicId: string | undefined,
|
||||||
transcriptId: string,
|
transcriptId: string,
|
||||||
): UseTopicWithWords => {
|
): UseTopicWithWords => {
|
||||||
const [response, setResponse] =
|
const {
|
||||||
useState<GetTranscriptTopicWithWordsPerSpeaker | null>(null);
|
data: response,
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
isLoading: loading,
|
||||||
const [error, setErrorState] = useState<Error | null>(null);
|
error,
|
||||||
const { setError } = useError();
|
refetch,
|
||||||
const api = useApi();
|
} = useTranscriptTopicsWithWordsPerSpeaker(
|
||||||
|
transcriptId || null,
|
||||||
|
topicId || null,
|
||||||
|
);
|
||||||
|
|
||||||
const [count, setCount] = useState(0);
|
// Type-safe return based on state
|
||||||
|
if (error) {
|
||||||
|
return {
|
||||||
|
error: error as Error,
|
||||||
|
loading: false,
|
||||||
|
response: null,
|
||||||
|
refetch,
|
||||||
|
} as ErrorTopicWithWords & { refetch: () => void };
|
||||||
|
}
|
||||||
|
|
||||||
const refetch = () => {
|
if (loading || !response) {
|
||||||
if (!loading) {
|
return {
|
||||||
setCount(count + 1);
|
response: response || null,
|
||||||
setLoading(true);
|
loading: true,
|
||||||
setErrorState(null);
|
error: false,
|
||||||
}
|
refetch,
|
||||||
};
|
} as LoadingTopicWithWords & { refetch: () => void };
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
return {
|
||||||
if (!transcriptId || !topicId || !api) return;
|
response,
|
||||||
|
loading: false,
|
||||||
setLoading(true);
|
error: null,
|
||||||
|
refetch,
|
||||||
api
|
} as SuccessTopicWithWords & { refetch: () => void };
|
||||||
.v1TranscriptGetTopicsWithWordsPerSpeaker({ transcriptId, topicId })
|
|
||||||
.then((result) => {
|
|
||||||
setResponse(result);
|
|
||||||
setLoading(false);
|
|
||||||
console.debug("Topics with words Loaded:", result);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
const shouldShowHuman = shouldShowError(error);
|
|
||||||
if (shouldShowHuman) {
|
|
||||||
setError(error, "There was an error loading the topics with words");
|
|
||||||
} else {
|
|
||||||
setError(error);
|
|
||||||
}
|
|
||||||
setErrorState(error);
|
|
||||||
});
|
|
||||||
}, [transcriptId, !api, topicId, count]);
|
|
||||||
|
|
||||||
return { response, loading, error, refetch } as UseTopicWithWords;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useTopicWithWords;
|
export default useTopicWithWords;
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useTranscriptTopics } from "../../lib/api-hooks";
|
||||||
|
|
||||||
import { useError } from "../../(errors)/errorContext";
|
|
||||||
import { Topic } from "./webSocketTypes";
|
|
||||||
import useApi from "../../lib/useApi";
|
|
||||||
import { shouldShowError } from "../../lib/errorUtils";
|
|
||||||
import { GetTranscriptTopic } from "../../lib/api-types";
|
import { GetTranscriptTopic } from "../../lib/api-types";
|
||||||
|
|
||||||
type TranscriptTopics = {
|
type TranscriptTopics = {
|
||||||
@@ -13,34 +8,13 @@ type TranscriptTopics = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useTopics = (id: string): TranscriptTopics => {
|
const useTopics = (id: string): TranscriptTopics => {
|
||||||
const [topics, setTopics] = useState<Topic[] | null>(null);
|
const { data: topics, isLoading: loading, error } = useTranscriptTopics(id);
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
const [error, setErrorState] = useState<Error | null>(null);
|
|
||||||
const { setError } = useError();
|
|
||||||
const api = useApi();
|
|
||||||
useEffect(() => {
|
|
||||||
if (!id || !api) return;
|
|
||||||
|
|
||||||
setLoading(true);
|
return {
|
||||||
api
|
topics: topics || null,
|
||||||
.v1TranscriptGetTopics({ transcriptId: id })
|
loading,
|
||||||
.then((result) => {
|
error: error as Error | null,
|
||||||
setTopics(result);
|
};
|
||||||
setLoading(false);
|
|
||||||
console.debug("Transcript topics loaded:", result);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setErrorState(err);
|
|
||||||
const shouldShowHuman = shouldShowError(err);
|
|
||||||
if (shouldShowHuman) {
|
|
||||||
setError(err, "There was an error loading the topics");
|
|
||||||
} else {
|
|
||||||
setError(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [id, !api]);
|
|
||||||
|
|
||||||
return { topics, loading, error };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useTopics;
|
export default useTopics;
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { AudioWaveform } from "../../lib/api-types";
|
import { AudioWaveform } from "../../lib/api-types";
|
||||||
import { useError } from "../../(errors)/errorContext";
|
import { useTranscriptWaveform } from "../../lib/api-hooks";
|
||||||
import useApi from "../../lib/useApi";
|
|
||||||
import { shouldShowError } from "../../lib/errorUtils";
|
|
||||||
|
|
||||||
type AudioWaveFormResponse = {
|
type AudioWaveFormResponse = {
|
||||||
waveform: AudioWaveform | null;
|
waveform: AudioWaveform | null;
|
||||||
@@ -11,35 +8,17 @@ type AudioWaveFormResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useWaveform = (id: string, skip: boolean): AudioWaveFormResponse => {
|
const useWaveform = (id: string, skip: boolean): AudioWaveFormResponse => {
|
||||||
const [waveform, setWaveform] = useState<AudioWaveform | null>(null);
|
const {
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
data: waveform,
|
||||||
const [error, setErrorState] = useState<Error | null>(null);
|
isLoading: loading,
|
||||||
const { setError } = useError();
|
error,
|
||||||
const api = useApi();
|
} = useTranscriptWaveform(skip ? null : id);
|
||||||
|
|
||||||
useEffect(() => {
|
return {
|
||||||
if (!id || !api || skip) {
|
waveform: waveform || null,
|
||||||
setLoading(false);
|
loading,
|
||||||
setErrorState(null);
|
error: error as Error | null,
|
||||||
setWaveform(null);
|
};
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
setErrorState(null);
|
|
||||||
api
|
|
||||||
.v1TranscriptGetAudioWaveform({ transcriptId: id })
|
|
||||||
.then((result) => {
|
|
||||||
setWaveform(result);
|
|
||||||
setLoading(false);
|
|
||||||
console.debug("Transcript waveform loaded:", result);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setErrorState(err);
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}, [id, api, skip]);
|
|
||||||
|
|
||||||
return { waveform, loading, error };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useWaveform;
|
export default useWaveform;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Peer from "simple-peer";
|
import Peer from "simple-peer";
|
||||||
import { useError } from "../../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
import useApi from "../../lib/useApi";
|
import { useTranscriptWebRTC } from "../../lib/api-hooks";
|
||||||
import { RtcOffer } from "../../lib/api-types";
|
import { RtcOffer } from "../../lib/api-types";
|
||||||
|
|
||||||
const useWebRTC = (
|
const useWebRTC = (
|
||||||
@@ -10,10 +10,10 @@ const useWebRTC = (
|
|||||||
): Peer => {
|
): Peer => {
|
||||||
const [peer, setPeer] = useState<Peer | null>(null);
|
const [peer, setPeer] = useState<Peer | null>(null);
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
const api = useApi();
|
const webRTCMutation = useTranscriptWebRTC();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!stream || !transcriptId || !api) {
|
if (!stream || !transcriptId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ const useWebRTC = (
|
|||||||
try {
|
try {
|
||||||
p = new Peer({ initiator: true, stream: stream });
|
p = new Peer({ initiator: true, stream: stream });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(error, "Error creating WebRTC");
|
setError(error as Error, "Error creating WebRTC");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,26 +32,31 @@ const useWebRTC = (
|
|||||||
setError(new Error(`WebRTC error: ${err}`));
|
setError(new Error(`WebRTC error: ${err}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
p.on("signal", (data: any) => {
|
p.on("signal", async (data: any) => {
|
||||||
if (!api) return;
|
|
||||||
if ("sdp" in data) {
|
if ("sdp" in data) {
|
||||||
const rtcOffer: RtcOffer = {
|
const rtcOffer: RtcOffer = {
|
||||||
sdp: data.sdp,
|
sdp: data.sdp,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
};
|
};
|
||||||
|
|
||||||
api
|
try {
|
||||||
.v1TranscriptRecordWebrtc({ transcriptId, requestBody: rtcOffer })
|
const answer = await webRTCMutation.mutateAsync({
|
||||||
.then((answer) => {
|
params: {
|
||||||
try {
|
path: {
|
||||||
p.signal(answer);
|
transcript_id: transcriptId,
|
||||||
} catch (error) {
|
},
|
||||||
setError(error);
|
},
|
||||||
}
|
body: rtcOffer,
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setError(error, "Error loading WebRTCOffer");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
p.signal(answer);
|
||||||
|
} catch (error) {
|
||||||
|
setError(error as Error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError(error as Error, "Error loading WebRTCOffer");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,7 +68,7 @@ const useWebRTC = (
|
|||||||
return () => {
|
return () => {
|
||||||
p.destroy();
|
p.destroy();
|
||||||
};
|
};
|
||||||
}, [stream, transcriptId, !api]);
|
}, [stream, transcriptId, webRTCMutation]);
|
||||||
|
|
||||||
return peer;
|
return peer;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { Topic, FinalSummary, Status } from "./webSocketTypes";
|
|||||||
import { useError } from "../../(errors)/errorContext";
|
import { useError } from "../../(errors)/errorContext";
|
||||||
import { DomainContext } from "../../domainContext";
|
import { DomainContext } from "../../domainContext";
|
||||||
import { AudioWaveform, GetTranscriptSegmentTopic } from "../../lib/api-types";
|
import { AudioWaveform, GetTranscriptSegmentTopic } from "../../lib/api-types";
|
||||||
import useApi from "../../lib/useApi";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { $api } from "../../lib/apiClient";
|
||||||
|
|
||||||
export type UseWebSockets = {
|
export type UseWebSockets = {
|
||||||
transcriptTextLive: string;
|
transcriptTextLive: string;
|
||||||
@@ -34,7 +35,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
|
|
||||||
const { websocket_url } = useContext(DomainContext);
|
const { websocket_url } = useContext(DomainContext);
|
||||||
const api = useApi();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [accumulatedText, setAccumulatedText] = useState<string>("");
|
const [accumulatedText, setAccumulatedText] = useState<string>("");
|
||||||
|
|
||||||
@@ -105,6 +106,13 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
title: "Topic 1: Introduction to Quantum Mechanics",
|
title: "Topic 1: Introduction to Quantum Mechanics",
|
||||||
transcript:
|
transcript:
|
||||||
"A brief overview of quantum mechanics and its principles.",
|
"A brief overview of quantum mechanics and its principles.",
|
||||||
|
segments: [
|
||||||
|
{
|
||||||
|
speaker: 1,
|
||||||
|
start: 0,
|
||||||
|
text: "This is the transcription of an example title",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "2",
|
id: "2",
|
||||||
@@ -315,9 +323,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!transcriptId || !api) return;
|
if (!transcriptId) return;
|
||||||
|
|
||||||
api?.v1TranscriptGetWebsocketEvents({ transcriptId }).then((result) => {});
|
|
||||||
|
|
||||||
const url = `${websocket_url}/v1/transcripts/${transcriptId}/events`;
|
const url = `${websocket_url}/v1/transcripts/${transcriptId}/events`;
|
||||||
let ws = new WebSocket(url);
|
let ws = new WebSocket(url);
|
||||||
@@ -361,6 +367,16 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
return [...prevTopics, topic];
|
return [...prevTopics, topic];
|
||||||
});
|
});
|
||||||
console.debug("TOPIC event:", message.data);
|
console.debug("TOPIC event:", message.data);
|
||||||
|
// Invalidate topics query to sync with WebSocket data
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: $api.queryOptions(
|
||||||
|
"get",
|
||||||
|
"/v1/transcripts/{transcript_id}/topics",
|
||||||
|
{
|
||||||
|
params: { path: { transcript_id: transcriptId } },
|
||||||
|
},
|
||||||
|
).queryKey,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "FINAL_SHORT_SUMMARY":
|
case "FINAL_SHORT_SUMMARY":
|
||||||
@@ -370,6 +386,16 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
case "FINAL_LONG_SUMMARY":
|
case "FINAL_LONG_SUMMARY":
|
||||||
if (message.data) {
|
if (message.data) {
|
||||||
setFinalSummary(message.data);
|
setFinalSummary(message.data);
|
||||||
|
// Invalidate transcript query to sync summary
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: $api.queryOptions(
|
||||||
|
"get",
|
||||||
|
"/v1/transcripts/{transcript_id}",
|
||||||
|
{
|
||||||
|
params: { path: { transcript_id: transcriptId } },
|
||||||
|
},
|
||||||
|
).queryKey,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -377,6 +403,16 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
console.debug("FINAL_TITLE event:", message.data);
|
console.debug("FINAL_TITLE event:", message.data);
|
||||||
if (message.data) {
|
if (message.data) {
|
||||||
setTitle(message.data.title);
|
setTitle(message.data.title);
|
||||||
|
// Invalidate transcript query to sync title
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: $api.queryOptions(
|
||||||
|
"get",
|
||||||
|
"/v1/transcripts/{transcript_id}",
|
||||||
|
{
|
||||||
|
params: { path: { transcript_id: transcriptId } },
|
||||||
|
},
|
||||||
|
).queryKey,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -450,7 +486,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
|
|||||||
return () => {
|
return () => {
|
||||||
ws.close();
|
ws.close();
|
||||||
};
|
};
|
||||||
}, [transcriptId, !api]);
|
}, [transcriptId, websocket_url, queryClient]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
transcriptTextLive,
|
transcriptTextLive,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { useRouter } from "next/navigation";
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import useSessionStatus from "../lib/useSessionStatus";
|
import useSessionStatus from "../lib/useSessionStatus";
|
||||||
import { useRecordingConsent } from "../recordingConsentContext";
|
import { useRecordingConsent } from "../recordingConsentContext";
|
||||||
import useApi from "../lib/useApi";
|
import { useMeetingAudioConsent } from "../lib/api-hooks";
|
||||||
import { Meeting } from "../lib/api-types";
|
import { Meeting } from "../lib/api-types";
|
||||||
import { FaBars } from "react-icons/fa6";
|
import { FaBars } from "react-icons/fa6";
|
||||||
|
|
||||||
@@ -76,31 +76,30 @@ const useConsentDialog = (
|
|||||||
wherebyRef: RefObject<HTMLElement> /*accessibility*/,
|
wherebyRef: RefObject<HTMLElement> /*accessibility*/,
|
||||||
) => {
|
) => {
|
||||||
const { state: consentState, touch, hasConsent } = useRecordingConsent();
|
const { state: consentState, touch, hasConsent } = useRecordingConsent();
|
||||||
const [consentLoading, setConsentLoading] = useState(false);
|
|
||||||
// toast would open duplicates, even with using "id=" prop
|
// toast would open duplicates, even with using "id=" prop
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const api = useApi();
|
const audioConsentMutation = useMeetingAudioConsent();
|
||||||
|
|
||||||
const handleConsent = useCallback(
|
const handleConsent = useCallback(
|
||||||
async (meetingId: string, given: boolean) => {
|
async (meetingId: string, given: boolean) => {
|
||||||
if (!api) return;
|
|
||||||
|
|
||||||
setConsentLoading(true);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.v1MeetingAudioConsent({
|
await audioConsentMutation.mutateAsync({
|
||||||
meetingId,
|
params: {
|
||||||
requestBody: { consent_given: given },
|
path: {
|
||||||
|
meeting_id: meetingId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
consent_given: given,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
touch(meetingId);
|
touch(meetingId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error submitting consent:", error);
|
console.error("Error submitting consent:", error);
|
||||||
} finally {
|
|
||||||
setConsentLoading(false);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[api, touch],
|
[audioConsentMutation, touch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const showConsentModal = useCallback(() => {
|
const showConsentModal = useCallback(() => {
|
||||||
@@ -194,7 +193,12 @@ const useConsentDialog = (
|
|||||||
return cleanup;
|
return cleanup;
|
||||||
}, [meetingId, handleConsent, wherebyRef, modalOpen]);
|
}, [meetingId, handleConsent, wherebyRef, modalOpen]);
|
||||||
|
|
||||||
return { showConsentModal, consentState, hasConsent, consentLoading };
|
return {
|
||||||
|
showConsentModal,
|
||||||
|
consentState,
|
||||||
|
hasConsent,
|
||||||
|
consentLoading: audioConsentMutation.isPending,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function ConsentDialogButton({
|
function ConsentDialogButton({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useError } from "../(errors)/errorContext";
|
import { useError } from "../(errors)/errorContext";
|
||||||
import { Meeting } from "../lib/api-types";
|
import { Meeting } from "../lib/api-types";
|
||||||
import { shouldShowError } from "../lib/errorUtils";
|
import { shouldShowError } from "../lib/errorUtils";
|
||||||
import useApi from "../lib/useApi";
|
import { useRoomsCreateMeeting } from "../lib/api-hooks";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
type ErrorMeeting = {
|
type ErrorMeeting = {
|
||||||
@@ -30,27 +30,25 @@ const useRoomMeeting = (
|
|||||||
roomName: string | null | undefined,
|
roomName: string | null | undefined,
|
||||||
): ErrorMeeting | LoadingMeeting | SuccessMeeting => {
|
): ErrorMeeting | LoadingMeeting | SuccessMeeting => {
|
||||||
const [response, setResponse] = useState<Meeting | null>(null);
|
const [response, setResponse] = useState<Meeting | null>(null);
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
|
||||||
const [error, setErrorState] = useState<Error | null>(null);
|
|
||||||
const [reload, setReload] = useState(0);
|
const [reload, setReload] = useState(0);
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
const api = useApi();
|
const createMeetingMutation = useRoomsCreateMeeting();
|
||||||
const reloadHandler = () => setReload((prev) => prev + 1);
|
const reloadHandler = () => setReload((prev) => prev + 1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!roomName || !api) return;
|
if (!roomName) return;
|
||||||
|
|
||||||
if (!response) {
|
const createMeeting = async () => {
|
||||||
setLoading(true);
|
try {
|
||||||
}
|
const result = await createMeetingMutation.mutateAsync({
|
||||||
|
params: {
|
||||||
api
|
path: {
|
||||||
.v1RoomsCreateMeeting({ roomName })
|
room_name: roomName,
|
||||||
.then((result) => {
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
setResponse(result);
|
setResponse(result);
|
||||||
setLoading(false);
|
} catch (error: any) {
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
const shouldShowHuman = shouldShowError(error);
|
const shouldShowHuman = shouldShowError(error);
|
||||||
if (shouldShowHuman && error.status !== 404) {
|
if (shouldShowHuman && error.status !== 404) {
|
||||||
setError(
|
setError(
|
||||||
@@ -60,9 +58,14 @@ const useRoomMeeting = (
|
|||||||
} else {
|
} else {
|
||||||
setError(error);
|
setError(error);
|
||||||
}
|
}
|
||||||
setErrorState(error);
|
}
|
||||||
});
|
};
|
||||||
}, [roomName, !api, reload]);
|
|
||||||
|
createMeeting();
|
||||||
|
}, [roomName, reload]);
|
||||||
|
|
||||||
|
const loading = createMeetingMutation.isPending && !response;
|
||||||
|
const error = createMeetingMutation.error as Error | null;
|
||||||
|
|
||||||
return { response, loading, error, reload: reloadHandler } as
|
return { response, loading, error, reload: reloadHandler } as
|
||||||
| ErrorMeeting
|
| ErrorMeeting
|
||||||
|
|||||||
@@ -9,20 +9,11 @@ import type { paths } from "../reflector-api";
|
|||||||
export function useRoomsList(page: number = 1) {
|
export function useRoomsList(page: number = 1) {
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery("get", "/v1/rooms", {
|
||||||
"get",
|
params: {
|
||||||
"/v1/rooms",
|
query: { page },
|
||||||
{
|
|
||||||
params: {
|
|
||||||
query: { page },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
onError: (error) => {
|
|
||||||
setError(error as Error, "There was an error fetching the rooms");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transcripts hooks
|
// Transcripts hooks
|
||||||
@@ -37,27 +28,17 @@ export function useTranscriptsSearch(
|
|||||||
) {
|
) {
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery("get", "/v1/transcripts/search", {
|
||||||
"get",
|
params: {
|
||||||
"/v1/transcripts/search",
|
query: {
|
||||||
{
|
q,
|
||||||
params: {
|
limit: options.limit,
|
||||||
query: {
|
offset: options.offset,
|
||||||
q,
|
room_id: options.room_id,
|
||||||
limit: options.limit,
|
source_kind: options.source_kind as any,
|
||||||
offset: options.offset,
|
|
||||||
room_id: options.room_id,
|
|
||||||
source_kind: options.source_kind as any,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
onError: (error) => {
|
|
||||||
setError(error as Error, "There was an error searching transcripts");
|
|
||||||
},
|
|
||||||
keepPreviousData: true, // For smooth pagination
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTranscriptDelete() {
|
export function useTranscriptDelete() {
|
||||||
@@ -68,7 +49,9 @@ export function useTranscriptDelete() {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// Invalidate transcripts queries to refetch
|
// Invalidate transcripts queries to refetch
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: $api.queryOptions("get", "/v1/transcripts/search").queryKey,
|
queryKey: $api.queryOptions("get", "/v1/transcripts/search", {
|
||||||
|
params: { query: { q: "" } },
|
||||||
|
}).queryKey,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@@ -102,9 +85,6 @@ export function useTranscriptGet(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId,
|
enabled: !!transcriptId,
|
||||||
onError: (error) => {
|
|
||||||
setError(error as Error, "There was an error loading the transcript");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -158,28 +138,21 @@ export function useRoomDelete() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zulip hooks
|
// Zulip hooks - NOTE: These endpoints are not in the OpenAPI spec yet
|
||||||
export function useZulipStreams() {
|
export function useZulipStreams() {
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
|
|
||||||
return $api.useQuery(
|
// @ts-ignore - Zulip endpoint not in OpenAPI spec
|
||||||
"get",
|
return $api.useQuery("get", "/v1/zulip/get-streams" as any, {});
|
||||||
"/v1/zulip/get-streams",
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
onError: (error) => {
|
|
||||||
setError(error as Error, "There was an error fetching Zulip streams");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useZulipTopics(streamId: number | null) {
|
export function useZulipTopics(streamId: number | null) {
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
|
|
||||||
|
// @ts-ignore - Zulip endpoint not in OpenAPI spec
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
"/v1/zulip/get-topics",
|
"/v1/zulip/get-topics" as any,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
query: { stream_id: streamId || 0 },
|
query: { stream_id: streamId || 0 },
|
||||||
@@ -187,9 +160,6 @@ export function useZulipTopics(streamId: number | null) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!streamId,
|
enabled: !!streamId,
|
||||||
onError: (error) => {
|
|
||||||
setError(error as Error, "There was an error fetching Zulip topics");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -219,11 +189,16 @@ export function useTranscriptUpdate() {
|
|||||||
export function useTranscriptPostToZulip() {
|
export function useTranscriptPostToZulip() {
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
|
|
||||||
return $api.useMutation("post", "/v1/transcripts/{transcript_id}/zulip", {
|
// @ts-ignore - Zulip endpoint not in OpenAPI spec
|
||||||
onError: (error) => {
|
return $api.useMutation(
|
||||||
setError(error as Error, "There was an error posting to Zulip");
|
"post",
|
||||||
|
"/v1/transcripts/{transcript_id}/zulip" as any,
|
||||||
|
{
|
||||||
|
onError: (error) => {
|
||||||
|
setError(error as Error, "There was an error posting to Zulip");
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTranscriptUploadAudio() {
|
export function useTranscriptUploadAudio() {
|
||||||
@@ -269,9 +244,6 @@ export function useTranscriptWaveform(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId,
|
enabled: !!transcriptId,
|
||||||
onError: (error) => {
|
|
||||||
setError(error as Error, "There was an error fetching the waveform");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -289,9 +261,6 @@ export function useTranscriptMP3(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId,
|
enabled: !!transcriptId,
|
||||||
onError: (error) => {
|
|
||||||
setError(error as Error, "There was an error fetching the MP3");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -309,9 +278,6 @@ export function useTranscriptTopics(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId,
|
enabled: !!transcriptId,
|
||||||
onError: (error) => {
|
|
||||||
setError(error as Error, "There was an error fetching topics");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -329,13 +295,30 @@ export function useTranscriptTopicsWithWords(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId,
|
enabled: !!transcriptId,
|
||||||
onError: (error) => {
|
},
|
||||||
setError(
|
);
|
||||||
error as Error,
|
}
|
||||||
"There was an error fetching topics with words",
|
|
||||||
);
|
export function useTranscriptTopicsWithWordsPerSpeaker(
|
||||||
|
transcriptId: string | null,
|
||||||
|
topicId: string | null,
|
||||||
|
) {
|
||||||
|
const { setError } = useError();
|
||||||
|
|
||||||
|
return $api.useQuery(
|
||||||
|
"get",
|
||||||
|
"/v1/transcripts/{transcript_id}/topics/{topic_id}/words-per-speaker",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
path: {
|
||||||
|
transcript_id: transcriptId || "",
|
||||||
|
topic_id: topicId || "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
enabled: !!transcriptId && !!topicId,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,9 +336,6 @@ export function useTranscriptParticipants(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId,
|
enabled: !!transcriptId,
|
||||||
onError: (error) => {
|
|
||||||
setError(error as Error, "There was an error fetching participants");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -389,12 +369,70 @@ export function useTranscriptParticipantUpdate() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTranscriptSpeakerAssign() {
|
export function useTranscriptParticipantCreate() {
|
||||||
const { setError } = useError();
|
const { setError } = useError();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return $api.useMutation(
|
return $api.useMutation(
|
||||||
"post",
|
"post",
|
||||||
|
"/v1/transcripts/{transcript_id}/participants",
|
||||||
|
{
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
// Invalidate participants list
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: $api.queryOptions(
|
||||||
|
"get",
|
||||||
|
"/v1/transcripts/{transcript_id}/participants",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
path: { transcript_id: variables.params.path.transcript_id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).queryKey,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setError(error as Error, "There was an error creating the participant");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTranscriptParticipantDelete() {
|
||||||
|
const { setError } = useError();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return $api.useMutation(
|
||||||
|
"delete",
|
||||||
|
"/v1/transcripts/{transcript_id}/participants/{participant_id}",
|
||||||
|
{
|
||||||
|
onSuccess: (data, variables) => {
|
||||||
|
// Invalidate participants list
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: $api.queryOptions(
|
||||||
|
"get",
|
||||||
|
"/v1/transcripts/{transcript_id}/participants",
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
path: { transcript_id: variables.params.path.transcript_id },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).queryKey,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setError(error as Error, "There was an error deleting the participant");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTranscriptSpeakerAssign() {
|
||||||
|
const { setError } = useError();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return $api.useMutation(
|
||||||
|
"patch",
|
||||||
"/v1/transcripts/{transcript_id}/speaker/assign",
|
"/v1/transcripts/{transcript_id}/speaker/assign",
|
||||||
{
|
{
|
||||||
onSuccess: (data, variables) => {
|
onSuccess: (data, variables) => {
|
||||||
@@ -434,7 +472,7 @@ export function useTranscriptSpeakerMerge() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return $api.useMutation(
|
return $api.useMutation(
|
||||||
"post",
|
"patch",
|
||||||
"/v1/transcripts/{transcript_id}/speaker/merge",
|
"/v1/transcripts/{transcript_id}/speaker/merge",
|
||||||
{
|
{
|
||||||
onSuccess: (data, variables) => {
|
onSuccess: (data, variables) => {
|
||||||
@@ -504,7 +542,9 @@ export function useTranscriptCreate() {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// Invalidate transcripts list
|
// Invalidate transcripts list
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: $api.queryOptions("get", "/v1/transcripts/search").queryKey,
|
queryKey: $api.queryOptions("get", "/v1/transcripts/search", {
|
||||||
|
params: { query: { q: "" } },
|
||||||
|
}).queryKey,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@@ -512,3 +552,21 @@ export function useTranscriptCreate() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rooms meeting operations
|
||||||
|
export function useRoomsCreateMeeting() {
|
||||||
|
const { setError } = useError();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return $api.useMutation("post", "/v1/rooms/{room_name}/meeting", {
|
||||||
|
onSuccess: () => {
|
||||||
|
// Invalidate rooms list to refresh meeting data
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: $api.queryOptions("get", "/v1/rooms").queryKey,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setError(error as Error, "There was an error creating the meeting");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ export type RtcOffer = components["schemas"]["RtcOffer"];
|
|||||||
export type GetTranscriptSegmentTopic =
|
export type GetTranscriptSegmentTopic =
|
||||||
components["schemas"]["GetTranscriptSegmentTopic"];
|
components["schemas"]["GetTranscriptSegmentTopic"];
|
||||||
export type Page_Room_ = components["schemas"]["Page_Room_"];
|
export type Page_Room_ = components["schemas"]["Page_Room_"];
|
||||||
export type ApiError = components["schemas"]["ApiError"];
|
|
||||||
export type GetTranscriptTopicWithWordsPerSpeaker =
|
export type GetTranscriptTopicWithWordsPerSpeaker =
|
||||||
components["schemas"]["GetTranscriptTopicWithWordsPerSpeaker"];
|
components["schemas"]["GetTranscriptTopicWithWordsPerSpeaker"];
|
||||||
export type GetTranscriptMinimal =
|
export type GetTranscriptMinimal =
|
||||||
@@ -24,5 +23,5 @@ export type GetTranscriptMinimal =
|
|||||||
|
|
||||||
// Export any enums or constants that were in the old API
|
// Export any enums or constants that were in the old API
|
||||||
export const $SourceKind = {
|
export const $SourceKind = {
|
||||||
values: ["SINGLE", "CALL", "WHEREBY", "UPLOAD"] as const,
|
values: ["room", "live", "file"] as const,
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
// Compatibility layer for direct API client usage
|
|
||||||
// Prefer using React Query hooks from api-hooks.ts instead
|
|
||||||
|
|
||||||
import { client } from "./apiClient";
|
|
||||||
|
|
||||||
// Returns the configured client for direct API calls
|
|
||||||
// This is a minimal compatibility layer for components that haven't been fully migrated
|
|
||||||
export default function useApi() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export the client directly for non-hook contexts
|
|
||||||
export { client };
|
|
||||||
Reference in New Issue
Block a user