mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-04-12 08:26:53 +00:00
feat: send email in share transcript and add email sending in room (#924)
* fix: add source language for file pipeline * feat: send email in share transcript and add email sending in room * fix: hide audio and video streaming for unauthenticated users * fix: security order
This commit is contained in:
committed by
GitHub
parent
74b9b97453
commit
e2ba502697
@@ -31,6 +31,7 @@ import {
|
||||
useZulipTopics,
|
||||
useRoomGet,
|
||||
useRoomTestWebhook,
|
||||
useConfig,
|
||||
} from "../../lib/apiHooks";
|
||||
import { RoomList } from "./_components/RoomList";
|
||||
import { PaginationPage } from "../browse/_components/Pagination";
|
||||
@@ -92,6 +93,7 @@ const roomInitialState = {
|
||||
icsFetchInterval: 5,
|
||||
platform: "whereby",
|
||||
skipConsent: false,
|
||||
emailTranscriptTo: "",
|
||||
};
|
||||
|
||||
export default function RoomsList() {
|
||||
@@ -133,11 +135,15 @@ export default function RoomsList() {
|
||||
null,
|
||||
);
|
||||
const [showWebhookSecret, setShowWebhookSecret] = useState(false);
|
||||
const [emailTranscriptEnabled, setEmailTranscriptEnabled] = useState(false);
|
||||
|
||||
const createRoomMutation = useRoomCreate();
|
||||
const updateRoomMutation = useRoomUpdate();
|
||||
const deleteRoomMutation = useRoomDelete();
|
||||
const { data: streams = [] } = useZulipStreams();
|
||||
const { data: config } = useConfig();
|
||||
const zulipEnabled = config?.zulip_enabled ?? false;
|
||||
const emailEnabled = config?.email_enabled ?? false;
|
||||
const { data: streams = [] } = useZulipStreams(zulipEnabled);
|
||||
const { data: topics = [] } = useZulipTopics(selectedStreamId);
|
||||
|
||||
const {
|
||||
@@ -177,6 +183,7 @@ export default function RoomsList() {
|
||||
icsFetchInterval: detailedEditedRoom.ics_fetch_interval || 5,
|
||||
platform: detailedEditedRoom.platform,
|
||||
skipConsent: detailedEditedRoom.skip_consent || false,
|
||||
emailTranscriptTo: detailedEditedRoom.email_transcript_to || "",
|
||||
}
|
||||
: null,
|
||||
[detailedEditedRoom],
|
||||
@@ -329,6 +336,7 @@ export default function RoomsList() {
|
||||
ics_fetch_interval: room.icsFetchInterval,
|
||||
platform,
|
||||
skip_consent: room.skipConsent,
|
||||
email_transcript_to: room.emailTranscriptTo || null,
|
||||
};
|
||||
|
||||
if (isEditing) {
|
||||
@@ -369,6 +377,7 @@ export default function RoomsList() {
|
||||
// Reset states
|
||||
setShowWebhookSecret(false);
|
||||
setWebhookTestResult(null);
|
||||
setEmailTranscriptEnabled(!!roomData.email_transcript_to);
|
||||
|
||||
setRoomInput({
|
||||
name: roomData.name,
|
||||
@@ -392,6 +401,7 @@ export default function RoomsList() {
|
||||
icsFetchInterval: roomData.ics_fetch_interval || 5,
|
||||
platform: roomData.platform,
|
||||
skipConsent: roomData.skip_consent || false,
|
||||
emailTranscriptTo: roomData.email_transcript_to || "",
|
||||
});
|
||||
setEditRoomId(roomId);
|
||||
setIsEditing(true);
|
||||
@@ -469,6 +479,7 @@ export default function RoomsList() {
|
||||
setNameError("");
|
||||
setShowWebhookSecret(false);
|
||||
setWebhookTestResult(null);
|
||||
setEmailTranscriptEnabled(false);
|
||||
onOpen();
|
||||
}}
|
||||
>
|
||||
@@ -504,7 +515,9 @@ export default function RoomsList() {
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="general">General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="calendar">Calendar</Tabs.Trigger>
|
||||
<Tabs.Trigger value="share">Share</Tabs.Trigger>
|
||||
{(zulipEnabled || emailEnabled) && (
|
||||
<Tabs.Trigger value="share">Share</Tabs.Trigger>
|
||||
)}
|
||||
<Tabs.Trigger value="webhook">WebHook</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
@@ -831,96 +844,144 @@ export default function RoomsList() {
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="share" pt={6}>
|
||||
<Field.Root>
|
||||
<Checkbox.Root
|
||||
name="zulipAutoPost"
|
||||
checked={room.zulipAutoPost}
|
||||
onCheckedChange={(e) => {
|
||||
const syntheticEvent = {
|
||||
target: {
|
||||
name: "zulipAutoPost",
|
||||
type: "checkbox",
|
||||
checked: e.checked,
|
||||
},
|
||||
};
|
||||
handleRoomChange(syntheticEvent);
|
||||
}}
|
||||
>
|
||||
<Checkbox.HiddenInput />
|
||||
<Checkbox.Control>
|
||||
<Checkbox.Indicator />
|
||||
</Checkbox.Control>
|
||||
<Checkbox.Label>
|
||||
Automatically post transcription to Zulip
|
||||
</Checkbox.Label>
|
||||
</Checkbox.Root>
|
||||
</Field.Root>
|
||||
<Field.Root mt={4}>
|
||||
<Field.Label>Zulip stream</Field.Label>
|
||||
<Select.Root
|
||||
value={room.zulipStream ? [room.zulipStream] : []}
|
||||
onValueChange={(e) =>
|
||||
setRoomInput({
|
||||
...room,
|
||||
zulipStream: e.value[0],
|
||||
zulipTopic: "",
|
||||
})
|
||||
}
|
||||
collection={streamCollection}
|
||||
disabled={!room.zulipAutoPost}
|
||||
>
|
||||
<Select.HiddenSelect />
|
||||
<Select.Control>
|
||||
<Select.Trigger>
|
||||
<Select.ValueText placeholder="Select stream" />
|
||||
</Select.Trigger>
|
||||
<Select.IndicatorGroup>
|
||||
<Select.Indicator />
|
||||
</Select.IndicatorGroup>
|
||||
</Select.Control>
|
||||
<Select.Positioner>
|
||||
<Select.Content>
|
||||
{streamOptions.map((option) => (
|
||||
<Select.Item key={option.value} item={option}>
|
||||
{option.label}
|
||||
<Select.ItemIndicator />
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select.Positioner>
|
||||
</Select.Root>
|
||||
</Field.Root>
|
||||
<Field.Root mt={4}>
|
||||
<Field.Label>Zulip topic</Field.Label>
|
||||
<Select.Root
|
||||
value={room.zulipTopic ? [room.zulipTopic] : []}
|
||||
onValueChange={(e) =>
|
||||
setRoomInput({ ...room, zulipTopic: e.value[0] })
|
||||
}
|
||||
collection={topicCollection}
|
||||
disabled={!room.zulipAutoPost}
|
||||
>
|
||||
<Select.HiddenSelect />
|
||||
<Select.Control>
|
||||
<Select.Trigger>
|
||||
<Select.ValueText placeholder="Select topic" />
|
||||
</Select.Trigger>
|
||||
<Select.IndicatorGroup>
|
||||
<Select.Indicator />
|
||||
</Select.IndicatorGroup>
|
||||
</Select.Control>
|
||||
<Select.Positioner>
|
||||
<Select.Content>
|
||||
{topicOptions.map((option) => (
|
||||
<Select.Item key={option.value} item={option}>
|
||||
{option.label}
|
||||
<Select.ItemIndicator />
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select.Positioner>
|
||||
</Select.Root>
|
||||
</Field.Root>
|
||||
{emailEnabled && (
|
||||
<>
|
||||
<Field.Root>
|
||||
<Checkbox.Root
|
||||
checked={emailTranscriptEnabled}
|
||||
onCheckedChange={(e) => {
|
||||
setEmailTranscriptEnabled(!!e.checked);
|
||||
if (!e.checked) {
|
||||
setRoomInput({
|
||||
...room,
|
||||
emailTranscriptTo: "",
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox.HiddenInput />
|
||||
<Checkbox.Control>
|
||||
<Checkbox.Indicator />
|
||||
</Checkbox.Control>
|
||||
<Checkbox.Label>
|
||||
Email me transcript when processed
|
||||
</Checkbox.Label>
|
||||
</Checkbox.Root>
|
||||
</Field.Root>
|
||||
{emailTranscriptEnabled && (
|
||||
<Field.Root mt={2}>
|
||||
<Input
|
||||
name="emailTranscriptTo"
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
value={room.emailTranscriptTo}
|
||||
onChange={handleRoomChange}
|
||||
/>
|
||||
<Field.HelperText>
|
||||
Transcript will be emailed to this address after
|
||||
processing
|
||||
</Field.HelperText>
|
||||
</Field.Root>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{zulipEnabled && (
|
||||
<>
|
||||
<Field.Root mt={emailEnabled ? 4 : 0}>
|
||||
<Checkbox.Root
|
||||
name="zulipAutoPost"
|
||||
checked={room.zulipAutoPost}
|
||||
onCheckedChange={(e) => {
|
||||
const syntheticEvent = {
|
||||
target: {
|
||||
name: "zulipAutoPost",
|
||||
type: "checkbox",
|
||||
checked: e.checked,
|
||||
},
|
||||
};
|
||||
handleRoomChange(syntheticEvent);
|
||||
}}
|
||||
>
|
||||
<Checkbox.HiddenInput />
|
||||
<Checkbox.Control>
|
||||
<Checkbox.Indicator />
|
||||
</Checkbox.Control>
|
||||
<Checkbox.Label>
|
||||
Automatically post transcription to Zulip
|
||||
</Checkbox.Label>
|
||||
</Checkbox.Root>
|
||||
</Field.Root>
|
||||
<Field.Root mt={4}>
|
||||
<Field.Label>Zulip stream</Field.Label>
|
||||
<Select.Root
|
||||
value={room.zulipStream ? [room.zulipStream] : []}
|
||||
onValueChange={(e) =>
|
||||
setRoomInput({
|
||||
...room,
|
||||
zulipStream: e.value[0],
|
||||
zulipTopic: "",
|
||||
})
|
||||
}
|
||||
collection={streamCollection}
|
||||
disabled={!room.zulipAutoPost}
|
||||
>
|
||||
<Select.HiddenSelect />
|
||||
<Select.Control>
|
||||
<Select.Trigger>
|
||||
<Select.ValueText placeholder="Select stream" />
|
||||
</Select.Trigger>
|
||||
<Select.IndicatorGroup>
|
||||
<Select.Indicator />
|
||||
</Select.IndicatorGroup>
|
||||
</Select.Control>
|
||||
<Select.Positioner>
|
||||
<Select.Content>
|
||||
{streamOptions.map((option) => (
|
||||
<Select.Item key={option.value} item={option}>
|
||||
{option.label}
|
||||
<Select.ItemIndicator />
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select.Positioner>
|
||||
</Select.Root>
|
||||
</Field.Root>
|
||||
<Field.Root mt={4}>
|
||||
<Field.Label>Zulip topic</Field.Label>
|
||||
<Select.Root
|
||||
value={room.zulipTopic ? [room.zulipTopic] : []}
|
||||
onValueChange={(e) =>
|
||||
setRoomInput({
|
||||
...room,
|
||||
zulipTopic: e.value[0],
|
||||
})
|
||||
}
|
||||
collection={topicCollection}
|
||||
disabled={!room.zulipAutoPost}
|
||||
>
|
||||
<Select.HiddenSelect />
|
||||
<Select.Control>
|
||||
<Select.Trigger>
|
||||
<Select.ValueText placeholder="Select topic" />
|
||||
</Select.Trigger>
|
||||
<Select.IndicatorGroup>
|
||||
<Select.Indicator />
|
||||
</Select.IndicatorGroup>
|
||||
</Select.Control>
|
||||
<Select.Positioner>
|
||||
<Select.Content>
|
||||
{topicOptions.map((option) => (
|
||||
<Select.Item key={option.value} item={option}>
|
||||
{option.label}
|
||||
<Select.ItemIndicator />
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select.Positioner>
|
||||
</Select.Root>
|
||||
</Field.Root>
|
||||
</>
|
||||
)}
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="webhook" pt={6}>
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import { useTranscriptGet } from "../../../lib/apiHooks";
|
||||
import { TranscriptStatus } from "../../../lib/transcript";
|
||||
import { useAuth } from "../../../lib/AuthProvider";
|
||||
import { featureEnabled } from "../../../lib/features";
|
||||
|
||||
type TranscriptDetails = {
|
||||
params: Promise<{
|
||||
@@ -57,7 +59,10 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
||||
const [finalSummaryElement, setFinalSummaryElement] =
|
||||
useState<HTMLDivElement | null>(null);
|
||||
|
||||
const hasCloudVideo = !!transcript.data?.has_cloud_video;
|
||||
const auth = useAuth();
|
||||
const isAuthenticated =
|
||||
auth.status === "authenticated" || !featureEnabled("requireLogin");
|
||||
const hasCloudVideo = !!transcript.data?.has_cloud_video && isAuthenticated;
|
||||
const [videoExpanded, setVideoExpanded] = useState(false);
|
||||
const [videoNewBadge, setVideoNewBadge] = useState(() => {
|
||||
if (typeof window === "undefined") return true;
|
||||
@@ -145,7 +150,7 @@ export default function TranscriptDetails(details: TranscriptDetails) {
|
||||
mt={4}
|
||||
mb={4}
|
||||
>
|
||||
{!mp3.audioDeleted && (
|
||||
{isAuthenticated && !mp3.audioDeleted && (
|
||||
<>
|
||||
{waveform.waveform && mp3.media && topics.topics ? (
|
||||
<Player
|
||||
|
||||
@@ -21,6 +21,10 @@ import { useAuth } from "../../../lib/AuthProvider";
|
||||
import { featureEnabled } from "../../../lib/features";
|
||||
import { SearchableLanguageSelect } from "../../../components/SearchableLanguageSelect";
|
||||
|
||||
const sourceLanguages = supportedLanguages.filter(
|
||||
(l) => l.value && l.value !== "NOTRANSLATION",
|
||||
);
|
||||
|
||||
const TranscriptCreate = () => {
|
||||
const router = useRouter();
|
||||
const auth = useAuth();
|
||||
@@ -33,8 +37,13 @@ const TranscriptCreate = () => {
|
||||
const nameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(event.target.value);
|
||||
};
|
||||
const [sourceLanguage, setSourceLanguage] = useState<string>("");
|
||||
const [targetLanguage, setTargetLanguage] = useState<string>("NOTRANSLATION");
|
||||
|
||||
const onSourceLanguageChange = (newval) => {
|
||||
(!newval || typeof newval === "string") &&
|
||||
setSourceLanguage(newval || "en");
|
||||
};
|
||||
const onLanguageChange = (newval) => {
|
||||
(!newval || typeof newval === "string") && setTargetLanguage(newval);
|
||||
};
|
||||
@@ -55,7 +64,7 @@ const TranscriptCreate = () => {
|
||||
const targetLang = getTargetLanguage();
|
||||
createTranscript.create({
|
||||
name,
|
||||
source_language: "en",
|
||||
source_language: sourceLanguage || "en",
|
||||
target_language: targetLang || "en",
|
||||
source_kind: "live",
|
||||
});
|
||||
@@ -67,7 +76,7 @@ const TranscriptCreate = () => {
|
||||
const targetLang = getTargetLanguage();
|
||||
createTranscript.create({
|
||||
name,
|
||||
source_language: "en",
|
||||
source_language: sourceLanguage || "en",
|
||||
target_language: targetLang || "en",
|
||||
source_kind: "file",
|
||||
});
|
||||
@@ -160,6 +169,15 @@ const TranscriptCreate = () => {
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</Box>
|
||||
<Box mb={4}>
|
||||
<Text mb={1}>Audio language</Text>
|
||||
<SearchableLanguageSelect
|
||||
options={sourceLanguages}
|
||||
value={sourceLanguage}
|
||||
onChange={onSourceLanguageChange}
|
||||
placeholder="Select language"
|
||||
/>
|
||||
</Box>
|
||||
<Box mb={4}>
|
||||
<Text mb={1}>Do you want to enable live translation?</Text>
|
||||
<SearchableLanguageSelect
|
||||
|
||||
@@ -18,10 +18,11 @@ import {
|
||||
createListCollection,
|
||||
} from "@chakra-ui/react";
|
||||
import { LuShare2 } from "react-icons/lu";
|
||||
import { useTranscriptUpdate } from "../../lib/apiHooks";
|
||||
import { useTranscriptUpdate, useConfig } from "../../lib/apiHooks";
|
||||
import ShareLink from "./shareLink";
|
||||
import ShareCopy from "./shareCopy";
|
||||
import ShareZulip from "./shareZulip";
|
||||
import ShareEmail from "./shareEmail";
|
||||
import { useAuth } from "../../lib/AuthProvider";
|
||||
|
||||
import { featureEnabled } from "../../lib/features";
|
||||
@@ -55,6 +56,9 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
||||
const [shareLoading, setShareLoading] = useState(false);
|
||||
const requireLogin = featureEnabled("requireLogin");
|
||||
const updateTranscriptMutation = useTranscriptUpdate();
|
||||
const { data: config } = useConfig();
|
||||
const zulipEnabled = config?.zulip_enabled ?? false;
|
||||
const emailEnabled = config?.email_enabled ?? false;
|
||||
|
||||
const updateShareMode = async (selectedValue: string) => {
|
||||
const selectedOption = shareOptionsData.find(
|
||||
@@ -169,14 +173,20 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
||||
<Text fontSize="sm" mb="2" fontWeight={"bold"}>
|
||||
Share options
|
||||
</Text>
|
||||
<Flex gap={2} mb={2}>
|
||||
{requireLogin && (
|
||||
<Flex gap={2} mb={2} flexWrap="wrap">
|
||||
{requireLogin && zulipEnabled && (
|
||||
<ShareZulip
|
||||
transcript={props.transcript}
|
||||
topics={props.topics}
|
||||
disabled={toShareMode(shareMode.value) === "private"}
|
||||
/>
|
||||
)}
|
||||
{emailEnabled && (
|
||||
<ShareEmail
|
||||
transcript={props.transcript}
|
||||
disabled={toShareMode(shareMode.value) === "private"}
|
||||
/>
|
||||
)}
|
||||
<ShareCopy
|
||||
finalSummaryElement={props.finalSummaryElement}
|
||||
transcript={props.transcript}
|
||||
|
||||
110
www/app/(app)/transcripts/shareEmail.tsx
Normal file
110
www/app/(app)/transcripts/shareEmail.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useState } from "react";
|
||||
import type { components } from "../../reflector-api";
|
||||
|
||||
type GetTranscriptWithParticipants =
|
||||
components["schemas"]["GetTranscriptWithParticipants"];
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
CloseButton,
|
||||
Input,
|
||||
Box,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
import { LuMail } from "react-icons/lu";
|
||||
import { useTranscriptSendEmail } from "../../lib/apiHooks";
|
||||
|
||||
type ShareEmailProps = {
|
||||
transcript: GetTranscriptWithParticipants;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
export default function ShareEmail(props: ShareEmailProps) {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [email, setEmail] = useState("");
|
||||
const [sent, setSent] = useState(false);
|
||||
const sendEmailMutation = useTranscriptSendEmail();
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!email) return;
|
||||
try {
|
||||
await sendEmailMutation.mutateAsync({
|
||||
params: {
|
||||
path: { transcript_id: props.transcript.id },
|
||||
},
|
||||
body: { email },
|
||||
});
|
||||
setSent(true);
|
||||
setTimeout(() => {
|
||||
setSent(false);
|
||||
setShowModal(false);
|
||||
setEmail("");
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error("Error sending email:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button disabled={props.disabled} onClick={() => setShowModal(true)}>
|
||||
<LuMail /> Send Email
|
||||
</Button>
|
||||
|
||||
<Dialog.Root
|
||||
open={showModal}
|
||||
onOpenChange={(e) => {
|
||||
setShowModal(e.open);
|
||||
if (!e.open) {
|
||||
setSent(false);
|
||||
setEmail("");
|
||||
}
|
||||
}}
|
||||
size="md"
|
||||
>
|
||||
<Dialog.Backdrop />
|
||||
<Dialog.Positioner>
|
||||
<Dialog.Content>
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Send Transcript via Email</Dialog.Title>
|
||||
<Dialog.CloseTrigger asChild>
|
||||
<CloseButton />
|
||||
</Dialog.CloseTrigger>
|
||||
</Dialog.Header>
|
||||
<Dialog.Body>
|
||||
{sent ? (
|
||||
<Text color="green.500">Email sent successfully!</Text>
|
||||
) : (
|
||||
<Box>
|
||||
<Text mb={2}>
|
||||
Enter the email address to send this transcript to:
|
||||
</Text>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="recipient@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSend()}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Dialog.Body>
|
||||
<Dialog.Footer>
|
||||
<Button variant="ghost" onClick={() => setShowModal(false)}>
|
||||
Close
|
||||
</Button>
|
||||
{!sent && (
|
||||
<Button
|
||||
disabled={!email || sendEmailMutation.isPending}
|
||||
onClick={handleSend}
|
||||
>
|
||||
{sendEmailMutation.isPending ? "Sending..." : "Send"}
|
||||
</Button>
|
||||
)}
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Positioner>
|
||||
</Dialog.Root>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -56,7 +56,7 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
|
||||
}, [navigator.serviceWorker, !serviceWorker, accessTokenInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!transcriptId || later || !transcript) return;
|
||||
if (!transcriptId || later || !transcript || !accessTokenInfo) return;
|
||||
|
||||
let stopped = false;
|
||||
let audioElement: HTMLAudioElement | null = null;
|
||||
@@ -113,7 +113,7 @@ const useMp3 = (transcriptId: string, waiting?: boolean): Mp3Response => {
|
||||
if (handleError) audioElement.removeEventListener("error", handleError);
|
||||
}
|
||||
};
|
||||
}, [transcriptId, transcript, later]);
|
||||
}, [transcriptId, transcript, later, accessTokenInfo]);
|
||||
|
||||
const getNow = () => {
|
||||
setLater(false);
|
||||
|
||||
@@ -39,17 +39,16 @@ export default function VideoPlayer({
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (accessToken) {
|
||||
params.set("token", accessToken);
|
||||
}
|
||||
const url = `${API_URL}/v1/transcripts/${transcriptId}/video/url?${params}`;
|
||||
const url = `${API_URL}/v1/transcripts/${transcriptId}/video/url`;
|
||||
const headers: Record<string, string> = {};
|
||||
if (accessToken) {
|
||||
headers["Authorization"] = `Bearer ${accessToken}`;
|
||||
}
|
||||
const resp = await fetch(url, { headers });
|
||||
if (!resp.ok) {
|
||||
if (resp.status === 401) {
|
||||
throw new Error("Sign in to view the video recording");
|
||||
}
|
||||
throw new Error("Failed to load video");
|
||||
}
|
||||
const data = await resp.json();
|
||||
@@ -90,7 +89,7 @@ export default function VideoPlayer({
|
||||
w="fit-content"
|
||||
maxW="100%"
|
||||
>
|
||||
<Text fontSize="sm">Failed to load video recording</Text>
|
||||
<Text fontSize="sm">{error || "Failed to load video recording"}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -132,10 +131,14 @@ export default function VideoPlayer({
|
||||
</Flex>
|
||||
</Flex>
|
||||
{/* Video element with visible controls */}
|
||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
||||
<video
|
||||
src={videoUrl}
|
||||
controls
|
||||
autoPlay
|
||||
controlsList="nodownload"
|
||||
disablePictureInPicture
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
style={{
|
||||
display: "block",
|
||||
width: "100%",
|
||||
|
||||
@@ -67,7 +67,7 @@ export function SearchableLanguageSelect({
|
||||
|
||||
const collection = useMemo(() => createListCollection({ items }), [items]);
|
||||
|
||||
const selectedValues = value && value !== "NOTRANSLATION" ? [value] : [];
|
||||
const selectedValues = value ? [value] : [];
|
||||
|
||||
return (
|
||||
<Combobox.Root
|
||||
|
||||
@@ -228,7 +228,11 @@ export function useRoomDelete() {
|
||||
});
|
||||
}
|
||||
|
||||
export function useZulipStreams() {
|
||||
export function useConfig() {
|
||||
return $api.useQuery("get", "/v1/config", {});
|
||||
}
|
||||
|
||||
export function useZulipStreams(enabled: boolean = true) {
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
@@ -236,7 +240,7 @@ export function useZulipStreams() {
|
||||
"/v1/zulip/streams",
|
||||
{},
|
||||
{
|
||||
enabled: isAuthenticated,
|
||||
enabled: enabled && isAuthenticated,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -291,6 +295,16 @@ export function useTranscriptPostToZulip() {
|
||||
});
|
||||
}
|
||||
|
||||
export function useTranscriptSendEmail() {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useMutation("post", "/v1/transcripts/{transcript_id}/email", {
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error sending the email");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useTranscriptUploadAudio() {
|
||||
const { setError } = useError();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
114
www/app/reflector-api.d.ts
vendored
114
www/app/reflector-api.d.ts
vendored
@@ -456,6 +456,23 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/transcripts/{transcript_id}/email": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Transcript Send Email */
|
||||
post: operations["v1_transcript_send_email"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/transcripts/{transcript_id}/audio/mp3": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -739,6 +756,23 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/config": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Get Config */
|
||||
get: operations["v1_get_config"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/zulip/streams": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -942,6 +976,13 @@ export interface components {
|
||||
*/
|
||||
updated_at: string;
|
||||
};
|
||||
/** ConfigResponse */
|
||||
ConfigResponse: {
|
||||
/** Zulip Enabled */
|
||||
zulip_enabled: boolean;
|
||||
/** Email Enabled */
|
||||
email_enabled: boolean;
|
||||
};
|
||||
/** CreateApiKeyRequest */
|
||||
CreateApiKeyRequest: {
|
||||
/** Name */
|
||||
@@ -1025,6 +1066,8 @@ export interface components {
|
||||
* @default false
|
||||
*/
|
||||
skip_consent: boolean;
|
||||
/** Email Transcript To */
|
||||
email_transcript_to?: string | null;
|
||||
};
|
||||
/** CreateRoomMeeting */
|
||||
CreateRoomMeeting: {
|
||||
@@ -1844,6 +1887,8 @@ export interface components {
|
||||
* @default false
|
||||
*/
|
||||
skip_consent: boolean;
|
||||
/** Email Transcript To */
|
||||
email_transcript_to?: string | null;
|
||||
};
|
||||
/** RoomDetails */
|
||||
RoomDetails: {
|
||||
@@ -1900,6 +1945,8 @@ export interface components {
|
||||
* @default false
|
||||
*/
|
||||
skip_consent: boolean;
|
||||
/** Email Transcript To */
|
||||
email_transcript_to?: string | null;
|
||||
/** Webhook Url */
|
||||
webhook_url: string | null;
|
||||
/** Webhook Secret */
|
||||
@@ -1984,6 +2031,16 @@ export interface components {
|
||||
/** Change Seq */
|
||||
change_seq?: number | null;
|
||||
};
|
||||
/** SendEmailRequest */
|
||||
SendEmailRequest: {
|
||||
/** Email */
|
||||
email: string;
|
||||
};
|
||||
/** SendEmailResponse */
|
||||
SendEmailResponse: {
|
||||
/** Sent */
|
||||
sent: number;
|
||||
};
|
||||
/**
|
||||
* SourceKind
|
||||
* @enum {string}
|
||||
@@ -2264,6 +2321,8 @@ export interface components {
|
||||
platform?: ("whereby" | "daily") | null;
|
||||
/** Skip Consent */
|
||||
skip_consent?: boolean | null;
|
||||
/** Email Transcript To */
|
||||
email_transcript_to?: string | null;
|
||||
};
|
||||
/** UpdateTranscript */
|
||||
UpdateTranscript: {
|
||||
@@ -3497,6 +3556,41 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
v1_transcript_send_email: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
transcript_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["SendEmailRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["SendEmailResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
v1_transcript_get_audio_mp3: {
|
||||
parameters: {
|
||||
query?: {
|
||||
@@ -4167,6 +4261,26 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
v1_get_config: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ConfigResponse"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
v1_zulip_get_streams: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Reference in New Issue
Block a user