mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
Remove domain segment
This commit is contained in:
259
www/app/(app)/transcripts/topicList.tsx
Normal file
259
www/app/(app)/transcripts/topicList.tsx
Normal file
@@ -0,0 +1,259 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { formatTime } from "../../lib/time";
|
||||
import ScrollToBottom from "./scrollToBottom";
|
||||
import { Topic } from "./webSocketTypes";
|
||||
import { generateHighContrastColor } from "../../lib/utils";
|
||||
import useParticipants from "./useParticipants";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Box,
|
||||
Flex,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { featureEnabled } from "../../domainContext";
|
||||
|
||||
type TopicListProps = {
|
||||
topics: Topic[];
|
||||
useActiveTopic: [
|
||||
Topic | null,
|
||||
React.Dispatch<React.SetStateAction<Topic | null>>,
|
||||
];
|
||||
autoscroll: boolean;
|
||||
transcriptId: string;
|
||||
status: string;
|
||||
currentTranscriptText: any;
|
||||
};
|
||||
|
||||
export function TopicList({
|
||||
topics,
|
||||
useActiveTopic,
|
||||
autoscroll,
|
||||
transcriptId,
|
||||
status,
|
||||
currentTranscriptText,
|
||||
}: TopicListProps) {
|
||||
const [activeTopic, setActiveTopic] = useActiveTopic;
|
||||
const [autoscrollEnabled, setAutoscrollEnabled] = useState<boolean>(true);
|
||||
const participants = useParticipants(transcriptId);
|
||||
|
||||
const scrollToTopic = () => {
|
||||
const topicDiv = document.getElementById(
|
||||
`accordion-button-topic-${activeTopic?.id}`,
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
topicDiv?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
inline: "nearest",
|
||||
});
|
||||
}, 200);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTopic) scrollToTopic();
|
||||
}, [activeTopic]);
|
||||
|
||||
// scroll top is not rounded, heights are, so exact match won't work.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
|
||||
const toggleScroll = (element) => {
|
||||
const bottom =
|
||||
Math.abs(
|
||||
element.scrollHeight - element.clientHeight - element.scrollTop,
|
||||
) < 2 || element.scrollHeight == element.clientHeight;
|
||||
if (!bottom && autoscrollEnabled) {
|
||||
setAutoscrollEnabled(false);
|
||||
} else if (bottom && !autoscrollEnabled) {
|
||||
setAutoscrollEnabled(true);
|
||||
}
|
||||
};
|
||||
const handleScroll = (e) => {
|
||||
toggleScroll(e.target);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (autoscroll) {
|
||||
const topicsDiv = document.getElementById("scroll-div");
|
||||
|
||||
topicsDiv && toggleScroll(topicsDiv);
|
||||
}
|
||||
}, [activeTopic, autoscroll]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoscroll && autoscrollEnabled) scrollToBottom();
|
||||
}, [topics.length, currentTranscriptText]);
|
||||
|
||||
const scrollToBottom = () => {
|
||||
const topicsDiv = document.getElementById("scroll-div");
|
||||
|
||||
if (topicsDiv) topicsDiv.scrollTop = topicsDiv.scrollHeight;
|
||||
};
|
||||
|
||||
const getSpeakerName = (speakerNumber: number) => {
|
||||
if (!participants.response) return;
|
||||
return (
|
||||
participants.response.find(
|
||||
(participant) => participant.speaker == speakerNumber,
|
||||
)?.name || `Speaker ${speakerNumber}`
|
||||
);
|
||||
};
|
||||
|
||||
const requireLogin = featureEnabled("requireLogin");
|
||||
|
||||
useEffect(() => {
|
||||
setActiveTopic(topics[topics.length - 1]);
|
||||
}, [topics]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
position={"relative"}
|
||||
w={"100%"}
|
||||
h={"95%"}
|
||||
flexDirection={"column"}
|
||||
justify={"center"}
|
||||
align={"center"}
|
||||
flexShrink={0}
|
||||
>
|
||||
{autoscroll && (
|
||||
<ScrollToBottom
|
||||
visible={!autoscrollEnabled}
|
||||
handleScrollBottom={scrollToBottom}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box
|
||||
id="scroll-div"
|
||||
overflowY={"auto"}
|
||||
h={"100%"}
|
||||
onScroll={handleScroll}
|
||||
width="full"
|
||||
padding={2}
|
||||
>
|
||||
{topics.length > 0 && (
|
||||
<Accordion
|
||||
index={topics.findIndex((topic) => topic.id == activeTopic?.id)}
|
||||
variant="custom"
|
||||
allowToggle
|
||||
>
|
||||
{topics.map((topic, index) => (
|
||||
<AccordionItem
|
||||
key={index}
|
||||
background={{
|
||||
base: "light",
|
||||
hover: "gray.100",
|
||||
focus: "gray.100",
|
||||
}}
|
||||
id={`topic-${topic.id}`}
|
||||
>
|
||||
<Flex dir="row" letterSpacing={".2"}>
|
||||
<AccordionButton
|
||||
onClick={() => {
|
||||
setActiveTopic(
|
||||
activeTopic?.id == topic.id ? null : topic,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<AccordionIcon />
|
||||
<Box as="span" textAlign="left" ml="1">
|
||||
{topic.title}{" "}
|
||||
<Text
|
||||
as="span"
|
||||
color="gray.500"
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
>
|
||||
[{formatTime(topic.timestamp)}] - [
|
||||
{formatTime(topic.timestamp + (topic.duration || 0))}]
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionButton>
|
||||
</Flex>
|
||||
<AccordionPanel>
|
||||
{topic.segments ? (
|
||||
<>
|
||||
{topic.segments.map((segment, index: number) => (
|
||||
<Text
|
||||
key={index}
|
||||
className="text-left text-slate-500 text-sm md:text-base"
|
||||
pb={2}
|
||||
lineHeight={"1.3"}
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
color={"gray.500"}
|
||||
fontFamily={"monospace"}
|
||||
fontSize={"sm"}
|
||||
>
|
||||
[{formatTime(segment.start)}]
|
||||
</Text>
|
||||
<Text
|
||||
as="span"
|
||||
fontWeight={"bold"}
|
||||
fontSize={"sm"}
|
||||
color={generateHighContrastColor(
|
||||
`Speaker ${segment.speaker}`,
|
||||
[96, 165, 250],
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
{getSpeakerName(segment.speaker)}:
|
||||
</Text>{" "}
|
||||
<span>{segment.text}</span>
|
||||
</Text>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<>{topic.transcript}</>
|
||||
)}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
)}
|
||||
|
||||
{status == "recording" && (
|
||||
<Box textAlign={"center"}>
|
||||
<Text>{currentTranscriptText}</Text>
|
||||
</Box>
|
||||
)}
|
||||
{(status == "recording" || status == "idle") &&
|
||||
currentTranscriptText.length == 0 &&
|
||||
topics.length == 0 && (
|
||||
<Box textAlign={"center"} textColor="gray">
|
||||
<Text>
|
||||
Full discussion transcript will appear here after you start
|
||||
recording.
|
||||
</Text>
|
||||
<Text>
|
||||
It may take up to 5 minutes of conversation to first appear.
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{status == "processing" && (
|
||||
<Box textAlign={"center"} textColor="gray">
|
||||
<Text>We are processing the recording, please wait.</Text>
|
||||
{!requireLogin && (
|
||||
<span>
|
||||
Please do not navigate away from the page during this time.
|
||||
</span>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{status == "ended" && topics.length == 0 && (
|
||||
<Box textAlign={"center"} textColor="gray">
|
||||
<Text>Recording has ended without topics being found.</Text>
|
||||
</Box>
|
||||
)}
|
||||
{status == "error" && (
|
||||
<Box textAlign={"center"} textColor="gray">
|
||||
<Text>There was an error processing your recording</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user