fix: anonymous users transcript permissions (#621)

* fix: public transcript visibility

* fix: transcript permissions frontend

* dead code removal

* chore: remove unused code

* fix search tests

* fix search tests

---------

Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
This commit is contained in:
Igor Monadical
2025-09-09 10:50:29 -04:00
committed by GitHub
parent 5a5b323382
commit f81fe9948a
18 changed files with 90 additions and 143 deletions

View File

@@ -23,7 +23,7 @@ from pydantic import (
from reflector.db import get_database from reflector.db import get_database
from reflector.db.rooms import rooms from reflector.db.rooms import rooms
from reflector.db.transcripts import SourceKind, transcripts from reflector.db.transcripts import SourceKind, TranscriptStatus, transcripts
from reflector.db.utils import is_postgresql from reflector.db.utils import is_postgresql
from reflector.logger import logger from reflector.logger import logger
from reflector.utils.string import NonEmptyString, try_parse_non_empty_string from reflector.utils.string import NonEmptyString, try_parse_non_empty_string
@@ -161,7 +161,7 @@ class SearchResult(BaseModel):
room_name: str | None = None room_name: str | None = None
source_kind: SourceKind source_kind: SourceKind
created_at: datetime created_at: datetime
status: str = Field(..., min_length=1) status: TranscriptStatus = Field(..., min_length=1)
rank: float = Field(..., ge=0, le=1) rank: float = Field(..., ge=0, le=1)
duration: NonNegativeFloat | None = Field(..., description="Duration in seconds") duration: NonNegativeFloat | None = Field(..., description="Duration in seconds")
search_snippets: list[str] = Field( search_snippets: list[str] = Field(

View File

@@ -215,14 +215,10 @@ async def rooms_create_meeting(
except (asyncpg.exceptions.UniqueViolationError, sqlite3.IntegrityError): except (asyncpg.exceptions.UniqueViolationError, sqlite3.IntegrityError):
# Another request already created a meeting for this room # Another request already created a meeting for this room
# Log this race condition occurrence # Log this race condition occurrence
logger.info(
"Race condition detected for room %s - fetching existing meeting",
room.name,
)
logger.warning( logger.warning(
"Whereby meeting %s was created but not used (resource leak) for room %s", "Race condition detected for room %s and meeting %s - fetching existing meeting",
whereby_meeting["meetingId"],
room.name, room.name,
whereby_meeting["meetingId"],
) )
# Fetch the meeting that was created by the other request # Fetch the meeting that was created by the other request
@@ -232,7 +228,9 @@ async def rooms_create_meeting(
if meeting is None: if meeting is None:
# Edge case: meeting was created but expired/deleted between checks # Edge case: meeting was created but expired/deleted between checks
logger.error( logger.error(
"Meeting disappeared after race condition for room %s", room.name "Meeting disappeared after race condition for room %s",
room.name,
exc_info=True,
) )
raise HTTPException( raise HTTPException(
status_code=503, detail="Unable to join meeting - please try again" status_code=503, detail="Unable to join meeting - please try again"

View File

@@ -350,8 +350,6 @@ async def transcript_update(
transcript = await transcripts_controller.get_by_id_for_http( transcript = await transcripts_controller.get_by_id_for_http(
transcript_id, user_id=user_id transcript_id, user_id=user_id
) )
if not transcript:
raise HTTPException(status_code=404, detail="Transcript not found")
values = info.dict(exclude_unset=True) values = info.dict(exclude_unset=True)
updated_transcript = await transcripts_controller.update(transcript, values) updated_transcript = await transcripts_controller.update(transcript, values)
return updated_transcript return updated_transcript

View File

@@ -58,7 +58,7 @@ async def test_empty_transcript_title_only_match():
"id": test_id, "id": test_id,
"name": "Empty Transcript", "name": "Empty Transcript",
"title": "Empty Meeting", "title": "Empty Meeting",
"status": "completed", "status": "ended",
"locked": False, "locked": False,
"duration": 0.0, "duration": 0.0,
"created_at": datetime.now(timezone.utc), "created_at": datetime.now(timezone.utc),
@@ -109,7 +109,7 @@ async def test_search_with_long_summary():
"id": test_id, "id": test_id,
"name": "Test Long Summary", "name": "Test Long Summary",
"title": "Regular Meeting", "title": "Regular Meeting",
"status": "completed", "status": "ended",
"locked": False, "locked": False,
"duration": 1800.0, "duration": 1800.0,
"created_at": datetime.now(timezone.utc), "created_at": datetime.now(timezone.utc),
@@ -165,7 +165,7 @@ async def test_postgresql_search_with_data():
"id": test_id, "id": test_id,
"name": "Test Search Transcript", "name": "Test Search Transcript",
"title": "Engineering Planning Meeting Q4 2024", "title": "Engineering Planning Meeting Q4 2024",
"status": "completed", "status": "ended",
"locked": False, "locked": False,
"duration": 1800.0, "duration": 1800.0,
"created_at": datetime.now(timezone.utc), "created_at": datetime.now(timezone.utc),
@@ -221,7 +221,7 @@ We need to implement PostgreSQL tsvector for better performance.""",
test_result = next((r for r in results if r.id == test_id), None) test_result = next((r for r in results if r.id == test_id), None)
if test_result: if test_result:
assert test_result.title == "Engineering Planning Meeting Q4 2024" assert test_result.title == "Engineering Planning Meeting Q4 2024"
assert test_result.status == "completed" assert test_result.status == "ended"
assert test_result.duration == 1800.0 assert test_result.duration == 1800.0
assert 0 <= test_result.rank <= 1, "Rank should be normalized to 0-1" assert 0 <= test_result.rank <= 1, "Rank should be normalized to 0-1"
@@ -268,7 +268,7 @@ def mock_db_result():
"title": "Test Transcript", "title": "Test Transcript",
"created_at": datetime(2024, 6, 15, tzinfo=timezone.utc), "created_at": datetime(2024, 6, 15, tzinfo=timezone.utc),
"duration": 3600.0, "duration": 3600.0,
"status": "completed", "status": "ended",
"user_id": "test-user", "user_id": "test-user",
"room_id": "room1", "room_id": "room1",
"source_kind": SourceKind.LIVE, "source_kind": SourceKind.LIVE,
@@ -433,7 +433,7 @@ class TestSearchResultModel:
room_id="room-456", room_id="room-456",
source_kind=SourceKind.ROOM, source_kind=SourceKind.ROOM,
created_at=datetime(2024, 6, 15, tzinfo=timezone.utc), created_at=datetime(2024, 6, 15, tzinfo=timezone.utc),
status="completed", status="ended",
rank=0.85, rank=0.85,
duration=1800.5, duration=1800.5,
search_snippets=["snippet 1", "snippet 2"], search_snippets=["snippet 1", "snippet 2"],
@@ -443,7 +443,7 @@ class TestSearchResultModel:
assert result.title == "Test Title" assert result.title == "Test Title"
assert result.user_id == "user-123" assert result.user_id == "user-123"
assert result.room_id == "room-456" assert result.room_id == "room-456"
assert result.status == "completed" assert result.status == "ended"
assert result.rank == 0.85 assert result.rank == 0.85
assert result.duration == 1800.5 assert result.duration == 1800.5
assert len(result.search_snippets) == 2 assert len(result.search_snippets) == 2
@@ -474,7 +474,7 @@ class TestSearchResultModel:
id="test-id", id="test-id",
source_kind=SourceKind.LIVE, source_kind=SourceKind.LIVE,
created_at=datetime(2024, 6, 15, 12, 30, 45, tzinfo=timezone.utc), created_at=datetime(2024, 6, 15, 12, 30, 45, tzinfo=timezone.utc),
status="completed", status="ended",
rank=0.9, rank=0.9,
duration=None, duration=None,
search_snippets=[], search_snippets=[],

View File

@@ -25,7 +25,7 @@ async def test_long_summary_snippet_prioritization():
"id": test_id, "id": test_id,
"name": "Test Snippet Priority", "name": "Test Snippet Priority",
"title": "Meeting About Projects", "title": "Meeting About Projects",
"status": "completed", "status": "ended",
"locked": False, "locked": False,
"duration": 1800.0, "duration": 1800.0,
"created_at": datetime.now(timezone.utc), "created_at": datetime.now(timezone.utc),
@@ -106,7 +106,7 @@ async def test_long_summary_only_search():
"id": test_id, "id": test_id,
"name": "Test Long Only", "name": "Test Long Only",
"title": "Standard Meeting", "title": "Standard Meeting",
"status": "completed", "status": "ended",
"locked": False, "locked": False,
"duration": 1800.0, "duration": 1800.0,
"created_at": datetime.now(timezone.utc), "created_at": datetime.now(timezone.utc),

View File

@@ -7,9 +7,10 @@ import {
FaMicrophone, FaMicrophone,
FaGear, FaGear,
} from "react-icons/fa6"; } from "react-icons/fa6";
import { TranscriptStatus } from "../../../lib/transcript";
interface TranscriptStatusIconProps { interface TranscriptStatusIconProps {
status: string; status: TranscriptStatus;
} }
export default function TranscriptStatusIcon({ export default function TranscriptStatusIcon({

View File

@@ -5,6 +5,7 @@ import useParticipants from "../../useParticipants";
import { Box, Flex, Text, Accordion } from "@chakra-ui/react"; import { Box, Flex, Text, Accordion } from "@chakra-ui/react";
import { featureEnabled } from "../../../../domainContext"; import { featureEnabled } from "../../../../domainContext";
import { TopicItem } from "./TopicItem"; import { TopicItem } from "./TopicItem";
import { TranscriptStatus } from "../../../../lib/transcript";
type TopicListProps = { type TopicListProps = {
topics: Topic[]; topics: Topic[];
@@ -14,7 +15,7 @@ type TopicListProps = {
]; ];
autoscroll: boolean; autoscroll: boolean;
transcriptId: string; transcriptId: string;
status: string; status: TranscriptStatus | null;
currentTranscriptText: any; currentTranscriptText: any;
}; };

View File

@@ -9,8 +9,10 @@ import ParticipantList from "./participantList";
import type { components } from "../../../../reflector-api"; import type { components } from "../../../../reflector-api";
type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"]; type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"];
import { SelectedText, selectedTextIsTimeSlice } from "./types"; import { SelectedText, selectedTextIsTimeSlice } from "./types";
import { useTranscriptUpdate } from "../../../../lib/apiHooks"; import {
import useTranscript from "../../useTranscript"; useTranscriptGet,
useTranscriptUpdate,
} from "../../../../lib/apiHooks";
import { useError } from "../../../../(errors)/errorContext"; import { useError } from "../../../../(errors)/errorContext";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Box, Grid } from "@chakra-ui/react"; import { Box, Grid } from "@chakra-ui/react";
@@ -25,7 +27,7 @@ export default function TranscriptCorrect({
params: { transcriptId }, params: { transcriptId },
}: TranscriptCorrect) { }: TranscriptCorrect) {
const updateTranscriptMutation = useTranscriptUpdate(); const updateTranscriptMutation = useTranscriptUpdate();
const transcript = useTranscript(transcriptId); const transcript = useTranscriptGet(transcriptId);
const stateCurrentTopic = useState<GetTranscriptTopic>(); const stateCurrentTopic = useState<GetTranscriptTopic>();
const [currentTopic, _sct] = stateCurrentTopic; const [currentTopic, _sct] = stateCurrentTopic;
const stateSelectedText = useState<SelectedText>(); const stateSelectedText = useState<SelectedText>();
@@ -36,7 +38,7 @@ export default function TranscriptCorrect({
const router = useRouter(); const router = useRouter();
const markAsDone = async () => { const markAsDone = async () => {
if (transcript.response && !transcript.response.reviewed) { if (transcript.data && !transcript.data.reviewed) {
try { try {
await updateTranscriptMutation.mutateAsync({ await updateTranscriptMutation.mutateAsync({
params: { params: {
@@ -114,7 +116,7 @@ export default function TranscriptCorrect({
}} }}
/> />
</Grid> </Grid>
{transcript.response && !transcript.response?.reviewed && ( {transcript.data && !transcript.data?.reviewed && (
<div className="flex flex-row justify-end"> <div className="flex flex-row justify-end">
<button <button
className="p-2 px-4 rounded bg-green-400" className="p-2 px-4 rounded bg-green-400"

View File

@@ -1,6 +1,5 @@
"use client"; "use client";
import Modal from "../modal"; import Modal from "../modal";
import useTranscript from "../useTranscript";
import useTopics from "../useTopics"; import useTopics from "../useTopics";
import useWaveform from "../useWaveform"; import useWaveform from "../useWaveform";
import useMp3 from "../useMp3"; import useMp3 from "../useMp3";
@@ -12,6 +11,8 @@ import TranscriptTitle from "../transcriptTitle";
import Player from "../player"; import Player from "../player";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Box, Flex, Grid, GridItem, Skeleton, Text } from "@chakra-ui/react"; import { Box, Flex, Grid, GridItem, Skeleton, Text } from "@chakra-ui/react";
import { useTranscriptGet } from "../../../lib/apiHooks";
import { TranscriptStatus } from "../../../lib/transcript";
type TranscriptDetails = { type TranscriptDetails = {
params: { params: {
@@ -22,11 +23,15 @@ type TranscriptDetails = {
export default function TranscriptDetails(details: TranscriptDetails) { export default function TranscriptDetails(details: TranscriptDetails) {
const transcriptId = details.params.transcriptId; const transcriptId = details.params.transcriptId;
const router = useRouter(); const router = useRouter();
const statusToRedirect = ["idle", "recording", "processing"]; const statusToRedirect = [
"idle",
"recording",
"processing",
] satisfies TranscriptStatus[] as TranscriptStatus[];
const transcript = useTranscript(transcriptId); const transcript = useTranscriptGet(transcriptId);
const transcriptStatus = transcript.response?.status; const waiting =
const waiting = statusToRedirect.includes(transcriptStatus || ""); transcript.data && statusToRedirect.includes(transcript.data.status);
const mp3 = useMp3(transcriptId, waiting); const mp3 = useMp3(transcriptId, waiting);
const topics = useTopics(transcriptId); const topics = useTopics(transcriptId);
@@ -56,7 +61,7 @@ export default function TranscriptDetails(details: TranscriptDetails) {
); );
} }
if (transcript?.loading || topics?.loading) { if (transcript?.isLoading || topics?.loading) {
return <Modal title="Loading" text={"Loading transcript..."} />; return <Modal title="Loading" text={"Loading transcript..."} />;
} }
@@ -86,7 +91,7 @@ export default function TranscriptDetails(details: TranscriptDetails) {
useActiveTopic={useActiveTopic} useActiveTopic={useActiveTopic}
waveform={waveform.waveform} waveform={waveform.waveform}
media={mp3.media} media={mp3.media}
mediaDuration={transcript.response?.duration || null} mediaDuration={transcript.data?.duration || null}
/> />
) : !mp3.loading && (waveform.error || mp3.error) ? ( ) : !mp3.loading && (waveform.error || mp3.error) ? (
<Box p={4} bg="red.100" borderRadius="md"> <Box p={4} bg="red.100" borderRadius="md">
@@ -116,10 +121,10 @@ export default function TranscriptDetails(details: TranscriptDetails) {
<Flex direction="column" gap={0}> <Flex direction="column" gap={0}>
<Flex alignItems="center" gap={2}> <Flex alignItems="center" gap={2}>
<TranscriptTitle <TranscriptTitle
title={transcript.response?.title || "Unnamed Transcript"} title={transcript.data?.title || "Unnamed Transcript"}
transcriptId={transcriptId} transcriptId={transcriptId}
onUpdate={(newTitle) => { onUpdate={(newTitle) => {
transcript.reload(); transcript.refetch().then(() => {});
}} }}
/> />
</Flex> </Flex>
@@ -136,23 +141,23 @@ export default function TranscriptDetails(details: TranscriptDetails) {
useActiveTopic={useActiveTopic} useActiveTopic={useActiveTopic}
autoscroll={false} autoscroll={false}
transcriptId={transcriptId} transcriptId={transcriptId}
status={transcript.response?.status} status={transcript.data?.status || null}
currentTranscriptText="" currentTranscriptText=""
/> />
{transcript.response && topics.topics ? ( {transcript.data && topics.topics ? (
<> <>
<FinalSummary <FinalSummary
transcriptResponse={transcript.response} transcriptResponse={transcript.data}
topicsResponse={topics.topics} topicsResponse={topics.topics}
onUpdate={(newSummary) => { onUpdate={() => {
transcript.reload(); transcript.refetch();
}} }}
/> />
</> </>
) : ( ) : (
<Flex justify={"center"} alignItems={"center"} h={"100%"}> <Flex justify={"center"} alignItems={"center"} h={"100%"}>
<div className="flex flex-col h-full justify-center content-center"> <div className="flex flex-col h-full justify-center content-center">
{transcript.response.status == "processing" ? ( {transcript?.data?.status == "processing" ? (
<Text>Loading Transcript</Text> <Text>Loading Transcript</Text>
) : ( ) : (
<Text> <Text>

View File

@@ -2,7 +2,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Recorder from "../../recorder"; import Recorder from "../../recorder";
import { TopicList } from "../_components/TopicList"; import { TopicList } from "../_components/TopicList";
import useTranscript from "../../useTranscript";
import { useWebSockets } from "../../useWebSockets"; import { useWebSockets } from "../../useWebSockets";
import { Topic } from "../../webSocketTypes"; import { Topic } from "../../webSocketTypes";
import { lockWakeState, releaseWakeState } from "../../../../lib/wakeLock"; import { lockWakeState, releaseWakeState } from "../../../../lib/wakeLock";
@@ -11,6 +10,8 @@ import useMp3 from "../../useMp3";
import WaveformLoading from "../../waveformLoading"; import WaveformLoading from "../../waveformLoading";
import { Box, Text, Grid, Heading, VStack, Flex } from "@chakra-ui/react"; import { Box, Text, Grid, Heading, VStack, Flex } from "@chakra-ui/react";
import LiveTrancription from "../../liveTranscription"; import LiveTrancription from "../../liveTranscription";
import { useTranscriptGet } from "../../../../lib/apiHooks";
import { TranscriptStatus } from "../../../../lib/transcript";
type TranscriptDetails = { type TranscriptDetails = {
params: { params: {
@@ -19,7 +20,7 @@ type TranscriptDetails = {
}; };
const TranscriptRecord = (details: TranscriptDetails) => { const TranscriptRecord = (details: TranscriptDetails) => {
const transcript = useTranscript(details.params.transcriptId); const transcript = useTranscriptGet(details.params.transcriptId);
const [transcriptStarted, setTranscriptStarted] = useState(false); const [transcriptStarted, setTranscriptStarted] = useState(false);
const useActiveTopic = useState<Topic | null>(null); const useActiveTopic = useState<Topic | null>(null);
@@ -29,8 +30,8 @@ const TranscriptRecord = (details: TranscriptDetails) => {
const router = useRouter(); const router = useRouter();
const [status, setStatus] = useState( const [status, setStatus] = useState<TranscriptStatus>(
webSockets.status.value || transcript.response?.status || "idle", webSockets.status?.value || transcript.data?.status || "idle",
); );
useEffect(() => { useEffect(() => {
@@ -41,7 +42,7 @@ const TranscriptRecord = (details: TranscriptDetails) => {
useEffect(() => { useEffect(() => {
//TODO HANDLE ERROR STATUS BETTER //TODO HANDLE ERROR STATUS BETTER
const newStatus = const newStatus =
webSockets.status.value || transcript.response?.status || "idle"; webSockets.status?.value || transcript.data?.status || "idle";
setStatus(newStatus); setStatus(newStatus);
if (newStatus && (newStatus == "ended" || newStatus == "error")) { if (newStatus && (newStatus == "ended" || newStatus == "error")) {
console.log(newStatus, "redirecting"); console.log(newStatus, "redirecting");
@@ -49,7 +50,7 @@ const TranscriptRecord = (details: TranscriptDetails) => {
const newUrl = "/transcripts/" + details.params.transcriptId; const newUrl = "/transcripts/" + details.params.transcriptId;
router.replace(newUrl); router.replace(newUrl);
} }
}, [webSockets.status.value, transcript.response?.status]); }, [webSockets.status?.value, transcript.data?.status]);
useEffect(() => { useEffect(() => {
if (webSockets.waveform && webSockets.waveform) mp3.getNow(); if (webSockets.waveform && webSockets.waveform) mp3.getNow();

View File

@@ -1,12 +1,12 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import useTranscript from "../../useTranscript";
import { useWebSockets } from "../../useWebSockets"; import { useWebSockets } from "../../useWebSockets";
import { lockWakeState, releaseWakeState } from "../../../../lib/wakeLock"; import { lockWakeState, releaseWakeState } from "../../../../lib/wakeLock";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import useMp3 from "../../useMp3"; import useMp3 from "../../useMp3";
import { Center, VStack, Text, Heading, Button } from "@chakra-ui/react"; import { Center, VStack, Text, Heading, Button } from "@chakra-ui/react";
import FileUploadButton from "../../fileUploadButton"; import FileUploadButton from "../../fileUploadButton";
import { useTranscriptGet } from "../../../../lib/apiHooks";
type TranscriptUpload = { type TranscriptUpload = {
params: { params: {
@@ -15,7 +15,7 @@ type TranscriptUpload = {
}; };
const TranscriptUpload = (details: TranscriptUpload) => { const TranscriptUpload = (details: TranscriptUpload) => {
const transcript = useTranscript(details.params.transcriptId); const transcript = useTranscriptGet(details.params.transcriptId);
const [transcriptStarted, setTranscriptStarted] = useState(false); const [transcriptStarted, setTranscriptStarted] = useState(false);
const webSockets = useWebSockets(details.params.transcriptId); const webSockets = useWebSockets(details.params.transcriptId);
@@ -25,13 +25,13 @@ const TranscriptUpload = (details: TranscriptUpload) => {
const router = useRouter(); const router = useRouter();
const [status_, setStatus] = useState( const [status_, setStatus] = useState(
webSockets.status.value || transcript.response?.status || "idle", webSockets.status?.value || transcript.data?.status || "idle",
); );
// status is obviously done if we have transcript // status is obviously done if we have transcript
const status = const status =
!transcript.loading && transcript.response?.status === "ended" !transcript.isLoading && transcript.data?.status === "ended"
? transcript.response?.status ? transcript.data?.status
: status_; : status_;
useEffect(() => { useEffect(() => {
@@ -43,9 +43,9 @@ const TranscriptUpload = (details: TranscriptUpload) => {
//TODO HANDLE ERROR STATUS BETTER //TODO HANDLE ERROR STATUS BETTER
// TODO deprecate webSockets.status.value / depend on transcript.response?.status from query lib // TODO deprecate webSockets.status.value / depend on transcript.response?.status from query lib
const newStatus = const newStatus =
transcript.response?.status === "ended" transcript.data?.status === "ended"
? "ended" ? "ended"
: webSockets.status.value || transcript.response?.status || "idle"; : webSockets.status?.value || transcript.data?.status || "idle";
setStatus(newStatus); setStatus(newStatus);
if (newStatus && (newStatus == "ended" || newStatus == "error")) { if (newStatus && (newStatus == "ended" || newStatus == "error")) {
console.log(newStatus, "redirecting"); console.log(newStatus, "redirecting");
@@ -53,7 +53,7 @@ const TranscriptUpload = (details: TranscriptUpload) => {
const newUrl = "/transcripts/" + details.params.transcriptId; const newUrl = "/transcripts/" + details.params.transcriptId;
router.replace(newUrl); router.replace(newUrl);
} }
}, [webSockets.status.value, transcript.response?.status]); }, [webSockets.status?.value, transcript.data?.status]);
useEffect(() => { useEffect(() => {
if (webSockets.waveform && webSockets.waveform) mp3.getNow(); if (webSockets.waveform && webSockets.waveform) mp3.getNow();

View File

@@ -11,10 +11,11 @@ import useAudioDevice from "./useAudioDevice";
import { Box, Flex, IconButton, Menu, RadioGroup } from "@chakra-ui/react"; import { Box, Flex, IconButton, Menu, RadioGroup } from "@chakra-ui/react";
import { LuScreenShare, LuMic, LuPlay, LuCircleStop } from "react-icons/lu"; import { LuScreenShare, LuMic, LuPlay, LuCircleStop } from "react-icons/lu";
import { RECORD_A_MEETING_URL } from "../../api/urls"; import { RECORD_A_MEETING_URL } from "../../api/urls";
import { TranscriptStatus } from "../../lib/transcript";
type RecorderProps = { type RecorderProps = {
transcriptId: string; transcriptId: string;
status: string; status: TranscriptStatus;
}; };
export default function Recorder(props: RecorderProps) { export default function Recorder(props: RecorderProps) {

View File

@@ -1,69 +0,0 @@
import type { components } from "../../reflector-api";
import { useTranscriptGet } from "../../lib/apiHooks";
type GetTranscript = components["schemas"]["GetTranscript"];
type ErrorTranscript = {
error: Error;
loading: false;
response: null;
reload: () => void;
};
type LoadingTranscript = {
response: null;
loading: true;
error: false;
reload: () => void;
};
type SuccessTranscript = {
response: GetTranscript;
loading: false;
error: null;
reload: () => void;
};
const useTranscript = (
id: string | null,
): ErrorTranscript | LoadingTranscript | SuccessTranscript => {
const { data, isLoading, error, refetch } = useTranscriptGet(id);
// Map to the expected return format
if (isLoading) {
return {
response: null,
loading: true,
error: false,
reload: refetch,
};
}
if (error) {
return {
error: error as Error,
loading: false,
response: null,
reload: refetch,
};
}
// Check if data is undefined or null
if (!data) {
return {
response: null,
loading: true,
error: false,
reload: refetch,
};
}
return {
response: data,
loading: false,
error: null,
reload: refetch,
};
};
export default useTranscript;

View File

@@ -16,7 +16,7 @@ export type UseWebSockets = {
title: string; title: string;
topics: Topic[]; topics: Topic[];
finalSummary: FinalSummary; finalSummary: FinalSummary;
status: Status; status: Status | null;
waveform: AudioWaveform | null; waveform: AudioWaveform | null;
duration: number | null; duration: number | null;
}; };
@@ -34,7 +34,7 @@ export const useWebSockets = (transcriptId: string | null): UseWebSockets => {
const [finalSummary, setFinalSummary] = useState<FinalSummary>({ const [finalSummary, setFinalSummary] = useState<FinalSummary>({
summary: "", summary: "",
}); });
const [status, setStatus] = useState<Status>({ value: "" }); const [status, setStatus] = useState<Status | null>(null);
const { setError } = useError(); const { setError } = useError();
const { websocket_url: websocketUrl } = useContext(DomainContext); const { websocket_url: websocketUrl } = useContext(DomainContext);

View File

@@ -1,4 +1,5 @@
import type { components } from "../../reflector-api"; import type { components } from "../../reflector-api";
import type { TranscriptStatus } from "../../lib/transcript";
type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"]; type GetTranscriptTopic = components["schemas"]["GetTranscriptTopic"];
@@ -13,7 +14,7 @@ export type FinalSummary = {
}; };
export type Status = { export type Status = {
value: string; value: TranscriptStatus;
}; };
export type TranslatedTopic = { export type TranslatedTopic = {

View File

@@ -96,8 +96,6 @@ export function useTranscriptProcess() {
} }
export function useTranscriptGet(transcriptId: string | null) { export function useTranscriptGet(transcriptId: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery( return $api.useQuery(
"get", "get",
"/v1/transcripts/{transcript_id}", "/v1/transcripts/{transcript_id}",
@@ -109,7 +107,7 @@ export function useTranscriptGet(transcriptId: string | null) {
}, },
}, },
{ {
enabled: !!transcriptId && isAuthenticated, enabled: !!transcriptId,
}, },
); );
} }
@@ -292,18 +290,16 @@ export function useTranscriptUploadAudio() {
} }
export function useTranscriptWaveform(transcriptId: string | null) { export function useTranscriptWaveform(transcriptId: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery( return $api.useQuery(
"get", "get",
"/v1/transcripts/{transcript_id}/audio/waveform", "/v1/transcripts/{transcript_id}/audio/waveform",
{ {
params: { params: {
path: { transcript_id: transcriptId || "" }, path: { transcript_id: transcriptId! },
}, },
}, },
{ {
enabled: !!transcriptId && isAuthenticated, enabled: !!transcriptId,
}, },
); );
} }
@@ -316,7 +312,7 @@ export function useTranscriptMP3(transcriptId: string | null) {
"/v1/transcripts/{transcript_id}/audio/mp3", "/v1/transcripts/{transcript_id}/audio/mp3",
{ {
params: { params: {
path: { transcript_id: transcriptId || "" }, path: { transcript_id: transcriptId! },
}, },
}, },
{ {
@@ -326,8 +322,6 @@ export function useTranscriptMP3(transcriptId: string | null) {
} }
export function useTranscriptTopics(transcriptId: string | null) { export function useTranscriptTopics(transcriptId: string | null) {
const { isAuthenticated } = useAuthReady();
return $api.useQuery( return $api.useQuery(
"get", "get",
"/v1/transcripts/{transcript_id}/topics", "/v1/transcripts/{transcript_id}/topics",
@@ -337,7 +331,7 @@ export function useTranscriptTopics(transcriptId: string | null) {
}, },
}, },
{ {
enabled: !!transcriptId && isAuthenticated, enabled: !!transcriptId,
}, },
); );
} }

View File

@@ -0,0 +1,5 @@
import { components } from "../reflector-api";
type ApiTranscriptStatus = components["schemas"]["GetTranscript"]["status"];
export type TranscriptStatus = ApiTranscriptStatus;

View File

@@ -926,8 +926,17 @@ export interface components {
source_kind: components["schemas"]["SourceKind"]; source_kind: components["schemas"]["SourceKind"];
/** Created At */ /** Created At */
created_at: string; created_at: string;
/** Status */ /**
status: string; * Status
* @enum {string}
*/
status:
| "idle"
| "uploaded"
| "recording"
| "processing"
| "error"
| "ended";
/** Rank */ /** Rank */
rank: number; rank: number;
/** /**