refactor: improve transcript list performance (#480)

* refactor: improve transcript list performance

* fix: sync openapi

* fix: frontend types

* fix: remove drop table _alembic_tmp_meeting

* fix: remove create table too

* fix: remove uq_recording_object_key
This commit is contained in:
2025-07-15 15:10:05 -06:00
committed by GitHub
parent 3d370336cc
commit 9deb717e5b
21 changed files with 470 additions and 126 deletions

View File

@@ -44,7 +44,7 @@ import {
import useTranscriptList from "../transcripts/useTranscriptList";
import useSessionUser from "../../lib/useSessionUser";
import NextLink from "next/link";
import { Room, GetTranscript } from "../../api";
import { Room, GetTranscriptMinimal } from "../../api";
import Pagination from "./pagination";
import { formatTimeMs } from "../../lib/time";
import useApi from "../../lib/useApi";
@@ -328,7 +328,7 @@ export default function TranscriptBrowser() {
</Tr>
</Thead>
<Tbody>
{response?.items?.map((item: GetTranscript) => (
{response?.items?.map((item: GetTranscriptMinimal) => (
<Tr key={item.id}>
<Td>
<Flex alignItems="start">
@@ -416,7 +416,7 @@ export default function TranscriptBrowser() {
</Box>
<Box display={{ base: "block", md: "none" }}>
<Stack spacing={2}>
{response?.items?.map((item: GetTranscript) => (
{response?.items?.map((item: GetTranscriptMinimal) => (
<Box key={item.id} borderWidth={1} p={4} borderRadius="md">
<Flex justify="space-between" alignItems="flex-start" gap="2">
<Box>

View File

@@ -193,7 +193,7 @@ export default function RoomsList() {
(err.body as any).detail == "Room name is not unique"
) {
setNameError(
"This room name is already taken. Please choose a different name."
"This room name is already taken. Please choose a different name.",
);
} else {
setNameError("An error occurred. Please try again.");
@@ -316,7 +316,7 @@ export default function RoomsList() {
options={roomModeOptions}
value={{
label: roomModeOptions.find(
(rm) => rm.value === room.roomMode
(rm) => rm.value === room.roomMode,
)?.label,
value: room.roomMode,
}}
@@ -335,7 +335,7 @@ export default function RoomsList() {
options={recordingTypeOptions}
value={{
label: recordingTypeOptions.find(
(rt) => rt.value === room.recordingType
(rt) => rt.value === room.recordingType,
)?.label,
value: room.recordingType,
}}
@@ -358,7 +358,7 @@ export default function RoomsList() {
options={recordingTriggerOptions}
value={{
label: recordingTriggerOptions.find(
(rt) => rt.value === room.recordingTrigger
(rt) => rt.value === room.recordingTrigger,
)?.label,
value: room.recordingTrigger,
}}

View File

@@ -183,17 +183,21 @@ const TopicPlayer = ({
setIsPlaying(false);
};
const isLoaded = !mp3.loading && !!topicTime
const isLoaded = !mp3.loading && !!topicTime;
const error = mp3.error;
if (error !== null) {
return <Text fontSize="sm" pt="1" pl="2">
Loading error: {error}
</Text>
return (
<Text fontSize="sm" pt="1" pl="2">
Loading error: {error}
</Text>
);
}
if (mp3.audioDeleted) {
return <Text fontSize="sm" pt="1" pl="2">
This topic file has been deleted.
</Text>
return (
<Text fontSize="sm" pt="1" pl="2">
This topic file has been deleted.
</Text>
);
}
return (
<Skeleton

View File

@@ -67,8 +67,6 @@ export default function TranscriptDetails(details: TranscriptDetails) {
);
}
return (
<>
<Grid
@@ -78,7 +76,10 @@ export default function TranscriptDetails(details: TranscriptDetails) {
mt={4}
mb={4}
>
{waveform.waveform && mp3.media && !mp3.audioDeleted && topics.topics ? (
{waveform.waveform &&
mp3.media &&
!mp3.audioDeleted &&
topics.topics ? (
<Player
topics={topics?.topics}
useActiveTopic={useActiveTopic}

View File

@@ -43,8 +43,7 @@ import {
Input,
} from "@chakra-ui/react";
const TranscriptCreate = () => {
const isClient = typeof window !== 'undefined';
const isClient = typeof window !== "undefined";
const router = useRouter();
const { isLoading, isAuthenticated } = useSessionStatus();
const requireLogin = featureEnabled("requireLogin");
@@ -186,9 +185,9 @@ const TranscriptCreate = () => {
<Spacer />
) : permissionDenied ? (
<Text className="">
Permission to use your microphone was denied, please change
the permission setting in your browser and refresh this
page.
Permission to use your microphone was denied, please
change the permission setting in your browser and refresh
this page.
</Text>
) : (
<Button

View File

@@ -124,7 +124,7 @@ const useAudioDevice = () => {
permissionDenied,
audioDevices,
getAudioStream,
requestPermission
requestPermission,
};
};

View File

@@ -14,9 +14,13 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
const [media, setMedia] = useState<HTMLMediaElement | null>(null);
const [later, setLater] = useState(waiting);
const [audioLoading, setAudioLoading] = useState<boolean>(true);
const [audioLoadingError, setAudioLoadingError] = useState<null | string>(null);
const [transcriptMetadataLoading, setTranscriptMetadataLoading] = useState<boolean>(true);
const [transcriptMetadataLoadingError, setTranscriptMetadataLoadingError] = useState<string | null>(null);
const [audioLoadingError, setAudioLoadingError] = useState<null | string>(
null,
);
const [transcriptMetadataLoading, setTranscriptMetadataLoading] =
useState<boolean>(true);
const [transcriptMetadataLoadingError, setTranscriptMetadataLoadingError] =
useState<string | null>(null);
const [audioDeleted, setAudioDeleted] = useState<boolean | null>(null);
const api = getApi();
const { api_url } = useContext(DomainContext);
@@ -47,28 +51,29 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
});
}, [navigator.serviceWorker, !serviceWorker, accessTokenInfo]);
useEffect(() => {
if (!transcriptId || !api || later) return;
let deleted: boolean | null = null;
setTranscriptMetadataLoading(true);
const audioElement = document.createElement("audio");
audioElement.src = `${api_url}/v1/transcripts/${transcriptId}/audio/mp3`;
audioElement.crossOrigin = "anonymous";
audioElement.preload = "auto";
const handleCanPlay = () => {
if (deleted) {
console.error('Illegal state: audio supposed to be deleted, but was loaded');
console.error(
"Illegal state: audio supposed to be deleted, but was loaded",
);
return;
}
setAudioLoading(false);
setAudioLoadingError(null);
};
const handleError = () => {
setAudioLoading(false);
if (deleted) {
@@ -77,18 +82,18 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
}
setAudioLoadingError("Failed to load audio");
};
audioElement.addEventListener('canplay', handleCanPlay);
audioElement.addEventListener('error', handleError);
setMedia(audioElement);
audioElement.addEventListener("canplay", handleCanPlay);
audioElement.addEventListener("error", handleError);
setMedia(audioElement);
setAudioLoading(true);
let stopped = false;
// Fetch transcript info in parallel
api.v1TranscriptGet({ transcriptId })
api
.v1TranscriptGet({ transcriptId })
.then((transcript) => {
if (stopped) return;
deleted = transcript.audio_deleted || false;
@@ -109,12 +114,12 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
.finally(() => {
if (stopped) return;
setTranscriptMetadataLoading(false);
})
});
return () => {
stopped = true;
audioElement.removeEventListener('canplay', handleCanPlay);
audioElement.removeEventListener('error', handleError);
audioElement.removeEventListener("canplay", handleCanPlay);
audioElement.removeEventListener("error", handleError);
};
}, [transcriptId, !api, later, api_url]);

View File

@@ -1,10 +1,10 @@
import { useEffect, useState } from "react";
import { useError } from "../../(errors)/errorContext";
import useApi from "../../lib/useApi";
import { Page_GetTranscript_, SourceKind } from "../../api";
import { Page_GetTranscriptMinimal_, SourceKind } from "../../api";
type TranscriptList = {
response: Page_GetTranscript_ | null;
response: Page_GetTranscriptMinimal_ | null;
loading: boolean;
error: Error | null;
refetch: () => void;
@@ -16,7 +16,9 @@ const useTranscriptList = (
roomId: string | null,
searchTerm: string | null,
): TranscriptList => {
const [response, setResponse] = useState<Page_GetTranscript_ | null>(null);
const [response, setResponse] = useState<Page_GetTranscriptMinimal_ | null>(
null,
);
const [loading, setLoading] = useState<boolean>(true);
const [error, setErrorState] = useState<Error | null>(null);
const { setError } = useError();