feat: AI Transcribe (#14140)
* chore: transcribe * chore: provider * chore: use daily provider * chore: rebase * chore: remove types file * chore: remove recoil * chore: progress * chore: add recoil * chore: save progress * chore: save progress * fix: type err * fix: err * fix: save progress * feat: finish emails * chore: add new env variable * chore: fix type err * chore: turbo * chore: improvements --------- Co-authored-by: Udit Takkar <udit222001@gmail.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
This commit is contained in:
co-authored by
Udit Takkar
Udit Takkar
Joe Au-Yeung
Peer Richelsen
Carina Wollendorfer
parent
ebca5c6409
commit
b9ac22d4ed
@@ -38,6 +38,7 @@ BASECAMP3_USER_AGENT=
|
||||
DAILY_API_KEY=
|
||||
DAILY_SCALE_PLAN=''
|
||||
DAILY_WEBHOOK_SECRET=''
|
||||
DAILY_MEETING_ENDED_WEBHOOK_SECRET=''
|
||||
|
||||
# - GOOGLE CALENDAR/MEET/LOGIN
|
||||
# Needed to enable Google Calendar integration and Login with Google
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useTranscription } from "@daily-co/daily-react";
|
||||
import { useDaily, useDailyEvent } from "@daily-co/daily-react";
|
||||
import React, { Fragment, useCallback, useRef, useState, useLayoutEffect, useEffect } from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { showToast } from "@calcom/ui";
|
||||
|
||||
export const CalAiTransctibe = () => {
|
||||
const daily = useDaily();
|
||||
const { t } = useLocale();
|
||||
|
||||
const [transcript, setTranscript] = useState("");
|
||||
|
||||
const [transcriptHeight, setTranscriptHeight] = useState(0);
|
||||
const transcriptRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const transcription = useTranscription();
|
||||
|
||||
useDailyEvent(
|
||||
"app-message",
|
||||
useCallback((ev) => {
|
||||
const data = ev?.data;
|
||||
if (data.user_name && data.text) setTranscript(`${data.user_name}: ${data.text}`);
|
||||
}, [])
|
||||
);
|
||||
|
||||
useDailyEvent("transcription-started", (ev) => {
|
||||
showToast(t("transcription_enabled"), "success");
|
||||
});
|
||||
|
||||
useDailyEvent("transcription-stopped", (ev) => {
|
||||
showToast(t("transcription_stopped"), "success");
|
||||
});
|
||||
|
||||
useDailyEvent("custom-button-click", (ev) => {
|
||||
if (ev?.button_id !== "transcription") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (transcription?.isTranscribing) {
|
||||
daily?.stopTranscription();
|
||||
} else {
|
||||
daily?.startTranscription();
|
||||
}
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
setTranscriptHeight(entry.target.scrollHeight);
|
||||
}
|
||||
});
|
||||
|
||||
if (transcriptRef.current) {
|
||||
observer.observe(transcriptRef.current);
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
transcriptRef.current?.scrollTo({
|
||||
top: transcriptRef.current?.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, [transcriptHeight]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toaster position="bottom-right" />
|
||||
<div
|
||||
id="cal-ai-thing"
|
||||
ref={transcriptRef}
|
||||
className="max-h-full overflow-x-hidden overflow-y-scroll p-2 text-center text-white">
|
||||
{transcript
|
||||
? transcript.split("\n").map((line, i) => (
|
||||
<Fragment key={`transcript-${i}`}>
|
||||
{i > 0 && <br />}
|
||||
{line}
|
||||
</Fragment>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import type { DailyCall } from "@daily-co/daily-js";
|
||||
import DailyIframe from "@daily-co/daily-js";
|
||||
import { DailyProvider } from "@daily-co/daily-react";
|
||||
import Head from "next/head";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { APP_NAME, SEO_IMG_OGIMG_VIDEO, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { APP_NAME, SEO_IMG_OGIMG_VIDEO, WEBSITE_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { formatToLocalizedDate, formatToLocalizedTime } from "@calcom/lib/date-fns";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import { Icon } from "@calcom/ui";
|
||||
|
||||
import { CalAiTransctibe } from "~/videos/ai/ai-transcribe";
|
||||
|
||||
import { type PageProps } from "./videos-single-view.getServerSideProps";
|
||||
|
||||
export default function JoinCall(props: PageProps) {
|
||||
const { t } = useLocale();
|
||||
const { meetingUrl, meetingPassword, booking } = props;
|
||||
const [daily, setDaily] = useState<DailyCall | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const callFrame = DailyIframe.createFrame({
|
||||
@@ -42,8 +47,20 @@ export default function JoinCall(props: PageProps) {
|
||||
},
|
||||
url: meetingUrl,
|
||||
...(typeof meetingPassword === "string" && { token: meetingPassword }),
|
||||
customTrayButtons: {
|
||||
transcription: {
|
||||
label: "Enable Transcription",
|
||||
tooltip: "Toggle Transcription",
|
||||
iconPath: `${WEBAPP_URL}/sparkles.svg`,
|
||||
iconPathDarkMode: `${WEBAPP_URL}/sparkles.svg`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setDaily(callFrame);
|
||||
|
||||
callFrame.join();
|
||||
|
||||
return () => {
|
||||
callFrame.destroy();
|
||||
};
|
||||
@@ -67,30 +84,35 @@ export default function JoinCall(props: PageProps) {
|
||||
<meta property="twitter:title" content={`${APP_NAME} Video`} />
|
||||
<meta property="twitter:description" content={t("quick_video_meeting")} />
|
||||
</Head>
|
||||
<div style={{ zIndex: 2, position: "relative" }}>
|
||||
{booking?.user?.organization?.calVideoLogo ? (
|
||||
<img
|
||||
className="min-w-16 min-h-16 fixed z-10 hidden aspect-square h-16 w-16 rounded-full sm:inline-block"
|
||||
src={booking.user.organization.calVideoLogo}
|
||||
alt="My Org Logo"
|
||||
style={{
|
||||
top: 32,
|
||||
left: 32,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="fixed z-10 hidden sm:inline-block"
|
||||
src={`${WEBSITE_URL}/cal-logo-word-dark.svg`}
|
||||
alt="Logo"
|
||||
style={{
|
||||
top: 32,
|
||||
left: 32,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<VideoMeetingInfo booking={booking} />
|
||||
<DailyProvider callObject={daily}>
|
||||
<div className="mx-auto" style={{ zIndex: 2, position: "absolute", bottom: 60, width: "100%" }}>
|
||||
<CalAiTransctibe />
|
||||
</div>
|
||||
<div style={{ zIndex: 2, position: "relative" }}>
|
||||
{booking?.user?.organization?.calVideoLogo ? (
|
||||
<img
|
||||
className="min-w-16 min-h-16 fixed z-10 hidden aspect-square h-16 w-16 rounded-full sm:inline-block"
|
||||
src={booking.user.organization.calVideoLogo}
|
||||
alt="My Org Logo"
|
||||
style={{
|
||||
top: 32,
|
||||
left: 32,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="fixed z-10 hidden sm:inline-block"
|
||||
src={`${WEBSITE_URL}/cal-logo-word-dark.svg`}
|
||||
alt="Logo"
|
||||
style={{
|
||||
top: 32,
|
||||
left: 32,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<VideoMeetingInfo booking={booking} />
|
||||
</DailyProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"@calcom/trpc": "*",
|
||||
"@calcom/tsconfig": "*",
|
||||
"@calcom/ui": "*",
|
||||
"@daily-co/daily-js": "^0.37.0",
|
||||
"@daily-co/daily-js": "^0.59.0",
|
||||
"@daily-co/daily-react": "^0.17.2",
|
||||
"@formkit/auto-animate": "1.0.0-beta.5",
|
||||
"@glidejs/glide": "^3.5.2",
|
||||
"@hookform/error-message": "^2.0.0",
|
||||
@@ -121,6 +122,7 @@
|
||||
"react-timezone-select": "^1.4.0",
|
||||
"react-turnstile": "^1.1.3",
|
||||
"react-use-intercom": "1.5.1",
|
||||
"recoil": "^0.7.7",
|
||||
"remove-markdown": "^0.5.0",
|
||||
"rrule": "^2.7.1",
|
||||
"sanitize-html": "^2.10.0",
|
||||
|
||||
@@ -12,6 +12,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
|
||||
|
||||
res.write(
|
||||
await renderEmail("MonthlyDigestEmail", {
|
||||
language: t,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["recorded-daily-video"] });
|
||||
const log = logger.getSubLogger({ prefix: ["daily-video-webhook-handler"] });
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
@@ -116,7 +116,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const response = schema.safeParse(req.body);
|
||||
|
||||
log.debug(
|
||||
"Recording Request Body:",
|
||||
"Daily video recording webhook Request Body:",
|
||||
safeStringify({
|
||||
response,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import { createHmac } from "crypto";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
import { getAllTranscriptsAccessLinkFromRoomName } from "@calcom/core/videoClient";
|
||||
import { sendDailyVideoTranscriptEmails } from "@calcom/emails";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { defaultHandler } from "@calcom/lib/server";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
const testRequestSchema = z.object({
|
||||
test: z.enum(["test"]),
|
||||
});
|
||||
|
||||
const log = logger.getSubLogger({ prefix: ["send-daily-video-transcript-handler"] });
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
version: z.string(),
|
||||
type: z.string(),
|
||||
id: z.string(),
|
||||
payload: z
|
||||
.object({
|
||||
meeting_id: z.string(),
|
||||
end_ts: z.number().optional(),
|
||||
room: z.string(),
|
||||
start_ts: z.number().optional(),
|
||||
})
|
||||
.passthrough(),
|
||||
event_ts: z.number().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (testRequestSchema.safeParse(req.body).success) {
|
||||
return res.status(200).json({ message: "Test request successful" });
|
||||
}
|
||||
|
||||
const hmacSecret = process.env.DAILY_MEETING_ENDED_WEBHOOK_SECRET;
|
||||
if (!hmacSecret) {
|
||||
return res.status(405).json({ message: "No Daily Webhook Secret" });
|
||||
}
|
||||
|
||||
const signature = `${req.headers["x-webhook-timestamp"]}.${JSON.stringify(req.body)}`;
|
||||
const base64DecodedSecret = Buffer.from(hmacSecret, "base64");
|
||||
const hmac = createHmac("sha256", base64DecodedSecret);
|
||||
const computed_signature = hmac.update(signature).digest("base64");
|
||||
|
||||
if (req.headers["x-webhook-signature"] !== computed_signature) {
|
||||
return res.status(403).json({ message: "Signature does not match" });
|
||||
}
|
||||
|
||||
const response = schema.safeParse(req.body);
|
||||
|
||||
log.debug(
|
||||
"Daily video transcript webhook Request Body:",
|
||||
safeStringify({
|
||||
response,
|
||||
})
|
||||
);
|
||||
|
||||
if (!response.success || response.data.type !== "meeting.ended") {
|
||||
return res.status(400).send({
|
||||
message: "Invalid Payload",
|
||||
});
|
||||
}
|
||||
|
||||
const { room, meeting_id } = response.data.payload;
|
||||
|
||||
try {
|
||||
const bookingReference = await prisma.bookingReference.findFirst({
|
||||
where: { type: "daily_video", uid: room, meetingId: room },
|
||||
select: { bookingId: true },
|
||||
});
|
||||
|
||||
if (!bookingReference || !bookingReference.bookingId) {
|
||||
log.error(
|
||||
"bookingReference Not found:",
|
||||
safeStringify({
|
||||
bookingReference,
|
||||
requestBody: req.body,
|
||||
})
|
||||
);
|
||||
return res.status(404).send({ message: "Booking reference not found" });
|
||||
}
|
||||
|
||||
const booking = await prisma.booking.findUniqueOrThrow({
|
||||
where: {
|
||||
id: bookingReference.bookingId,
|
||||
},
|
||||
select: {
|
||||
...bookingMinimalSelect,
|
||||
uid: true,
|
||||
location: true,
|
||||
isRecorded: true,
|
||||
eventTypeId: true,
|
||||
eventType: {
|
||||
select: {
|
||||
teamId: true,
|
||||
parentId: true,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
timeZone: true,
|
||||
email: true,
|
||||
name: true,
|
||||
locale: true,
|
||||
destinationCalendar: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!booking) {
|
||||
log.error(
|
||||
"Booking Not Found:",
|
||||
safeStringify({
|
||||
booking,
|
||||
requestBody: req.body,
|
||||
})
|
||||
);
|
||||
|
||||
return res.status(404).send({
|
||||
message: `Booking of room_name ${room} does not exist or does not contain daily video as location`,
|
||||
});
|
||||
}
|
||||
|
||||
const transcripts = await getAllTranscriptsAccessLinkFromRoomName(room);
|
||||
|
||||
if (!transcripts || !transcripts.length)
|
||||
return res.status(200).json({ message: `No Transcripts found for room name ${room}` });
|
||||
|
||||
const t = await getTranslation(booking?.user?.locale ?? "en", "common");
|
||||
const attendeesListPromises = booking.attendees.map(async (attendee) => {
|
||||
return {
|
||||
id: attendee.id,
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
timeZone: attendee.timeZone,
|
||||
language: {
|
||||
translate: await getTranslation(attendee.locale ?? "en", "common"),
|
||||
locale: attendee.locale ?? "en",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const attendeesList = await Promise.all(attendeesListPromises);
|
||||
|
||||
// Send emails
|
||||
const evt: CalendarEvent = {
|
||||
type: booking.title,
|
||||
title: booking.title,
|
||||
description: booking.description || undefined,
|
||||
startTime: booking.startTime.toISOString(),
|
||||
endTime: booking.endTime.toISOString(),
|
||||
organizer: {
|
||||
email: booking?.userPrimaryEmail || booking.user?.email || "Email-less",
|
||||
name: booking.user?.name || "Nameless",
|
||||
timeZone: booking.user?.timeZone || "Europe/London",
|
||||
language: { translate: t, locale: booking?.user?.locale ?? "en" },
|
||||
},
|
||||
attendees: attendeesList,
|
||||
uid: booking.uid,
|
||||
};
|
||||
|
||||
await sendDailyVideoTranscriptEmails(evt, transcripts);
|
||||
|
||||
return res.status(200).json({ message: "Success" });
|
||||
} catch (err) {
|
||||
console.error("Error in /send-daily-video-transcript", err);
|
||||
return res.status(500).json({ message: "something went wrong" });
|
||||
}
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
POST: Promise.resolve({ default: handler }),
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f4f4f4" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles"><path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/><path d="M5 3v4"/><path d="M19 17v4"/><path d="M3 5h4"/><path d="M17 19h4"/></svg>
|
||||
|
After Width: | Height: | Size: 467 B |
@@ -19,6 +19,7 @@
|
||||
"verify_email_banner_body": "Verify your email address to guarantee the best email and calendar deliverability",
|
||||
"verify_email_email_header": "Verify your email address",
|
||||
"verify_email_email_button": "Verify email",
|
||||
"cal_ai_assistant":"Cal AI Assistant",
|
||||
"verify_email_change_description": "You have recently requested to change the email address you use to log into your {{appName}} account. Please click the button below to confirm your new email address.",
|
||||
"verify_email_change_success_toast": "Updated your email to {{email}}",
|
||||
"verify_email_change_failure_toast": "Failed to update email.",
|
||||
@@ -110,7 +111,9 @@
|
||||
"event_still_awaiting_approval": "An event is still waiting for your approval",
|
||||
"booking_submitted_subject": "Booking Submitted: {{title}} at {{date}}",
|
||||
"download_recording_subject": "Download Recording: {{title}} at {{date}}",
|
||||
"download_transcript_email_subject":"Download Transcript: {{title}} at {{date}}",
|
||||
"download_your_recording": "Download your recording",
|
||||
"download_your_transcripts":"Download your Transcripts",
|
||||
"your_meeting_has_been_booked": "Your meeting has been booked",
|
||||
"event_type_has_been_rescheduled_on_time_date": "Your {{title}} has been rescheduled to {{date}}.",
|
||||
"event_has_been_rescheduled": "Updated - Your event has been rescheduled",
|
||||
@@ -1429,7 +1432,11 @@
|
||||
"download_responses_description": "Download all responses to your form in CSV format.",
|
||||
"download": "Download",
|
||||
"download_recording": "Download Recording",
|
||||
"transcription_enabled": "Transcriptions are enabled now",
|
||||
"transcription_stopped": "Transcriptions are stopped now",
|
||||
"download_transcript": "Download Transcript",
|
||||
"recording_from_your_recent_call": "A recording from your recent call on {{appName}} is ready for download",
|
||||
"transcript_from_previous_call": "Transcript from your recent call on {{appName}} is ready to download. Links are valid only for 1 Hour",
|
||||
"link_valid_for_12_hrs":"Note: The download link is valid only for 12 hours. You can generate new download link by following instructions <1>here</1>.",
|
||||
"create_your_first_form": "Create your first form",
|
||||
"create_your_first_form_description": "With Routing Forms you can ask qualifying questions and route to the correct person or event type.",
|
||||
|
||||
@@ -108,6 +108,8 @@
|
||||
"vitest-mock-extended": "^1.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@daily-co/daily-js": "^0.59.0",
|
||||
"city-timezones": "^1.2.1",
|
||||
"eslint": "^8.34.0",
|
||||
"turbo": "^1.10.1"
|
||||
},
|
||||
|
||||
@@ -30,9 +30,30 @@ const dailyReturnTypeSchema = z.object({
|
||||
enable_chat: z.boolean(),
|
||||
enable_knocking: z.boolean(),
|
||||
enable_prejoin_ui: z.boolean(),
|
||||
enable_transcription_storage: z.boolean(),
|
||||
}),
|
||||
});
|
||||
|
||||
const getTranscripts = z.object({
|
||||
total_count: z.number(),
|
||||
data: z.array(
|
||||
z.object({
|
||||
transcriptId: z.string(),
|
||||
domainId: z.string(),
|
||||
roomId: z.string(),
|
||||
mtgSessionId: z.string(),
|
||||
duration: z.number(),
|
||||
status: z.string(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
const getRooms = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
export interface DailyEventResult {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -86,6 +107,33 @@ function postToDailyAPI(endpoint: string, body: Record<string, unknown>) {
|
||||
});
|
||||
}
|
||||
|
||||
async function processTranscriptsInBatches(transcriptIds: Array<string>) {
|
||||
const batchSize = 5; // Batch size
|
||||
const batches = []; // Array to hold batches of transcript IDs
|
||||
|
||||
// Split transcript IDs into batches
|
||||
for (let i = 0; i < transcriptIds.length; i += batchSize) {
|
||||
batches.push(transcriptIds.slice(i, i + batchSize));
|
||||
}
|
||||
|
||||
const allTranscriptsAccessLinks = []; // Array to hold all access links
|
||||
|
||||
// Process each batch sequentially
|
||||
for (const batch of batches) {
|
||||
const batchPromises = batch.map((id) =>
|
||||
fetcher(`/transcript/${id}/access-link`)
|
||||
.then(z.object({ link: z.string() }).parse)
|
||||
.then((res) => res.link)
|
||||
);
|
||||
|
||||
const accessLinks = await Promise.all(batchPromises);
|
||||
|
||||
allTranscriptsAccessLinks.push(...accessLinks);
|
||||
}
|
||||
|
||||
return allTranscriptsAccessLinks;
|
||||
}
|
||||
|
||||
const DailyVideoApiAdapter = (): VideoApiAdapter => {
|
||||
async function createOrUpdateMeeting(endpoint: string, event: CalendarEvent): Promise<VideoCallData> {
|
||||
if (!event.uid) {
|
||||
@@ -120,19 +168,18 @@ const DailyVideoApiAdapter = (): VideoApiAdapter => {
|
||||
},
|
||||
},
|
||||
});
|
||||
if (scalePlan === "true" && !!hasTeamPlan === true) {
|
||||
return {
|
||||
privacy: "public",
|
||||
properties: {
|
||||
enable_prejoin_ui: true,
|
||||
enable_knocking: true,
|
||||
enable_screenshare: true,
|
||||
enable_chat: true,
|
||||
exp: exp,
|
||||
enable_recording: "cloud",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Check if organizer has subscribed to Cal.ai
|
||||
|
||||
const isCalAiSubscribed = await prisma.credential.findMany({
|
||||
where: {
|
||||
userId: event.organizer.id,
|
||||
type: "cal-ai_automation",
|
||||
invalid: false,
|
||||
paymentStatus: "active",
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
privacy: "public",
|
||||
properties: {
|
||||
@@ -141,6 +188,11 @@ const DailyVideoApiAdapter = (): VideoApiAdapter => {
|
||||
enable_screenshare: true,
|
||||
enable_chat: true,
|
||||
exp: exp,
|
||||
enable_recording: scalePlan === "true" && !!hasTeamPlan === true ? "cloud" : undefined,
|
||||
enable_transcription_storage: !!isCalAiSubscribed,
|
||||
permissions: {
|
||||
canAdmin: ["transcription"],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -159,6 +211,7 @@ const DailyVideoApiAdapter = (): VideoApiAdapter => {
|
||||
exp: exp,
|
||||
enable_recording: "cloud",
|
||||
start_video_off: true,
|
||||
enable_transcription_storage: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -210,6 +263,24 @@ const DailyVideoApiAdapter = (): VideoApiAdapter => {
|
||||
throw new Error("Something went wrong! Unable to get recording access link");
|
||||
}
|
||||
},
|
||||
getAllTranscriptsAccessLinkFromRoomName: async (roomName: string): Promise<Array<string>> => {
|
||||
try {
|
||||
const res = await fetcher(`/rooms/${roomName}`).then(getRooms.parse);
|
||||
const roomId = res.id;
|
||||
const allTranscripts = await fetcher(`/transcript?roomId=${roomId}`).then(getTranscripts.parse);
|
||||
|
||||
const allTranscriptsIds = allTranscripts.data.map((transcript) => transcript.transcriptId);
|
||||
|
||||
const allTranscriptsAccessLink = await processTranscriptsInBatches(allTranscriptsIds);
|
||||
|
||||
const accessLinks = await Promise.all(allTranscriptsAccessLink);
|
||||
|
||||
return Promise.resolve(accessLinks);
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
throw new Error("Something went wrong! Unable to get transcription access link");
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -274,6 +274,29 @@ const getDownloadLinkOfCalVideoByRecordingId = async (recordingId: string) => {
|
||||
return videoAdapter?.getRecordingDownloadLink?.(recordingId);
|
||||
};
|
||||
|
||||
const getAllTranscriptsAccessLinkFromRoomName = async (roomName: string) => {
|
||||
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
|
||||
try {
|
||||
dailyAppKeys = await getDailyAppKeys();
|
||||
} catch (e) {
|
||||
console.error("Error: Cal video provider is not installed.");
|
||||
return;
|
||||
}
|
||||
const [videoAdapter] = await getVideoAdapters([
|
||||
{
|
||||
id: 0,
|
||||
appId: "daily-video",
|
||||
type: "daily_video",
|
||||
userId: null,
|
||||
user: { email: "" },
|
||||
teamId: null,
|
||||
key: dailyAppKeys,
|
||||
invalid: false,
|
||||
},
|
||||
]);
|
||||
return videoAdapter?.getAllTranscriptsAccessLinkFromRoomName?.(roomName);
|
||||
};
|
||||
|
||||
export {
|
||||
getBusyVideoTimes,
|
||||
createMeeting,
|
||||
@@ -281,4 +304,5 @@ export {
|
||||
deleteMeeting,
|
||||
getRecordingsOfCalVideoByRoomName,
|
||||
getDownloadLinkOfCalVideoByRecordingId,
|
||||
getAllTranscriptsAccessLinkFromRoomName,
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import AttendeeAwaitingPaymentEmail from "./templates/attendee-awaiting-payment-
|
||||
import AttendeeCancelledEmail from "./templates/attendee-cancelled-email";
|
||||
import AttendeeCancelledSeatEmail from "./templates/attendee-cancelled-seat-email";
|
||||
import AttendeeDailyVideoDownloadRecordingEmail from "./templates/attendee-daily-video-download-recording-email";
|
||||
import AttendeeDailyVideoDownloadTranscriptEmail from "./templates/attendee-daily-video-download-transcript-email";
|
||||
import AttendeeDeclinedEmail from "./templates/attendee-declined-email";
|
||||
import AttendeeLocationChangeEmail from "./templates/attendee-location-change-email";
|
||||
import AttendeeRequestEmail from "./templates/attendee-request-email";
|
||||
@@ -46,6 +47,7 @@ import OrganizationEmailVerification from "./templates/organization-email-verifi
|
||||
import OrganizerAttendeeCancelledSeatEmail from "./templates/organizer-attendee-cancelled-seat-email";
|
||||
import OrganizerCancelledEmail from "./templates/organizer-cancelled-email";
|
||||
import OrganizerDailyVideoDownloadRecordingEmail from "./templates/organizer-daily-video-download-recording-email";
|
||||
import OrganizerDailyVideoDownloadTranscriptEmail from "./templates/organizer-daily-video-download-transcript-email";
|
||||
import OrganizerLocationChangeEmail from "./templates/organizer-location-change-email";
|
||||
import OrganizerPaymentRefundFailedEmail from "./templates/organizer-payment-refund-failed-email";
|
||||
import OrganizerRequestEmail from "./templates/organizer-request-email";
|
||||
@@ -481,6 +483,19 @@ export const sendDailyVideoRecordingEmails = async (calEvent: CalendarEvent, dow
|
||||
await Promise.all(emailsToSend);
|
||||
};
|
||||
|
||||
export const sendDailyVideoTranscriptEmails = async (calEvent: CalendarEvent, transcripts: string[]) => {
|
||||
const emailsToSend: Promise<unknown>[] = [];
|
||||
|
||||
emailsToSend.push(sendEmail(() => new OrganizerDailyVideoDownloadTranscriptEmail(calEvent, transcripts)));
|
||||
|
||||
for (const attendee of calEvent.attendees) {
|
||||
emailsToSend.push(
|
||||
sendEmail(() => new AttendeeDailyVideoDownloadTranscriptEmail(calEvent, attendee, transcripts))
|
||||
);
|
||||
}
|
||||
await Promise.all(emailsToSend);
|
||||
};
|
||||
|
||||
export const sendOrganizationEmailVerification = async (sendOrgInput: OrganizationEmailVerify) => {
|
||||
await sendEmail(() => new OrganizationEmailVerification(sendOrgInput));
|
||||
};
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import type { TFunction } from "next-i18next";
|
||||
|
||||
import { WEBAPP_URL, APP_NAME, COMPANY_NAME } from "@calcom/lib/constants";
|
||||
|
||||
import { V2BaseEmailHtml, CallToAction } from "../components";
|
||||
|
||||
interface DailyVideoDownloadTranscriptEmailProps {
|
||||
language: TFunction;
|
||||
transcriptDownloadLinks: Array<string>;
|
||||
title: string;
|
||||
date: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const DailyVideoDownloadTranscriptEmail = (
|
||||
props: DailyVideoDownloadTranscriptEmailProps & Partial<React.ComponentProps<typeof V2BaseEmailHtml>>
|
||||
) => {
|
||||
const image = `${WEBAPP_URL}/emails/logo.png`;
|
||||
return (
|
||||
<V2BaseEmailHtml
|
||||
subject={props.language("download_transcript_email_subject", {
|
||||
title: props.title,
|
||||
date: props.date,
|
||||
})}>
|
||||
<div style={{ width: "89px", marginBottom: "35px" }}>
|
||||
<a href={WEBAPP_URL} target="_blank" rel="noreferrer">
|
||||
<img
|
||||
height="19"
|
||||
src={image}
|
||||
style={{
|
||||
border: "0",
|
||||
display: "block",
|
||||
outline: "none",
|
||||
textDecoration: "none",
|
||||
height: "19px",
|
||||
width: "100%",
|
||||
fontSize: "13px",
|
||||
}}
|
||||
width="89"
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "32px",
|
||||
fontWeight: "600",
|
||||
lineHeight: "38.5px",
|
||||
marginBottom: "40px",
|
||||
color: "black",
|
||||
}}>
|
||||
<>{props.language("download_your_transcripts")}</>
|
||||
</p>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px" }}>
|
||||
<>{props.language("hi_user_name", { name: props.name })},</>
|
||||
</p>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px", marginBottom: "40px" }}>
|
||||
<>{props.language("transcript_from_previous_call", { appName: APP_NAME })}</>
|
||||
</p>
|
||||
|
||||
{props.transcriptDownloadLinks.map((downloadLink, index) => {
|
||||
return (
|
||||
<div
|
||||
key={downloadLink}
|
||||
style={{
|
||||
backgroundColor: "#F3F4F6",
|
||||
padding: "32px",
|
||||
marginBottom: "40px",
|
||||
}}>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "18px",
|
||||
lineHeight: "20px",
|
||||
fontWeight: 600,
|
||||
marginBottom: "8px",
|
||||
color: "black",
|
||||
}}>
|
||||
<>{props.title}</>
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "24px",
|
||||
marginBottom: "24px",
|
||||
marginTop: "0px",
|
||||
color: "black",
|
||||
}}>
|
||||
{props.date} Transcript {index + 1}
|
||||
</p>
|
||||
<CallToAction label={props.language("download_transcript")} href={downloadLink} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px", marginTop: "32px", marginBottom: "8px" }}>
|
||||
<>{props.language("happy_scheduling")},</>
|
||||
</p>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px", marginTop: "0px" }}>
|
||||
<>{props.language("the_calcom_team", { companyName: COMPANY_NAME })}</>
|
||||
</p>
|
||||
</V2BaseEmailHtml>
|
||||
);
|
||||
};
|
||||
@@ -27,6 +27,7 @@ export { VerifyAccountEmail } from "./VerifyAccountEmail";
|
||||
export { VerifyEmailByCode } from "./VerifyEmailByCode";
|
||||
export * from "@calcom/app-store/routing-forms/emails/components";
|
||||
export { DailyVideoDownloadRecordingEmail } from "./DailyVideoDownloadRecordingEmail";
|
||||
export { DailyVideoDownloadTranscriptEmail } from "./DailyVideoDownloadTranscriptEmail";
|
||||
export { OrganisationAccountVerifyEmail } from "./OrganizationAccountVerifyEmail";
|
||||
export { OrgAutoInviteEmail } from "./OrgAutoInviteEmail";
|
||||
export { MonthlyDigestEmail } from "./MonthlyDigestEmail";
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { TFunction } from "next-i18next";
|
||||
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "../";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class AttendeeDailyVideoDownloadTranscriptEmail extends BaseEmail {
|
||||
calEvent: CalendarEvent;
|
||||
attendee: Person;
|
||||
transcriptDownloadLinks: Array<string>;
|
||||
t: TFunction;
|
||||
|
||||
constructor(calEvent: CalendarEvent, attendee: Person, transcriptDownloadLinks: string[]) {
|
||||
super();
|
||||
this.name = "SEND_TRANSCRIPT_DOWNLOAD_LINK";
|
||||
this.calEvent = calEvent;
|
||||
this.attendee = attendee;
|
||||
this.transcriptDownloadLinks = transcriptDownloadLinks;
|
||||
this.t = attendee.language.translate;
|
||||
}
|
||||
protected async getNodeMailerPayload(): Promise<Record<string, unknown>> {
|
||||
return {
|
||||
to: `${this.attendee.name} <${this.attendee.email}>`,
|
||||
from: `${this.calEvent.organizer.name} <${this.getMailerOptions().from}>`,
|
||||
replyTo: [...this.calEvent.attendees.map(({ email }) => email), this.calEvent.organizer.email],
|
||||
subject: `${this.t("download_transcript_email_subject", {
|
||||
title: this.calEvent.title,
|
||||
date: this.getFormattedDate(),
|
||||
})}`,
|
||||
html: await renderEmail("DailyVideoDownloadTranscriptEmail", {
|
||||
title: this.calEvent.title,
|
||||
date: this.getFormattedDate(),
|
||||
transcriptDownloadLinks: this.transcriptDownloadLinks,
|
||||
language: this.t,
|
||||
name: this.attendee.name,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTimezone(): string {
|
||||
return this.attendee.timeZone;
|
||||
}
|
||||
|
||||
protected getLocale(): string {
|
||||
return this.attendee.language.locale;
|
||||
}
|
||||
|
||||
protected getInviteeStart(format: string) {
|
||||
return this.getFormattedRecipientTime({
|
||||
time: this.calEvent.startTime,
|
||||
format,
|
||||
});
|
||||
}
|
||||
|
||||
protected getInviteeEnd(format: string) {
|
||||
return this.getFormattedRecipientTime({
|
||||
time: this.calEvent.endTime,
|
||||
format,
|
||||
});
|
||||
}
|
||||
|
||||
protected getFormattedDate() {
|
||||
const inviteeTimeFormat = this.attendee.timeFormat || TimeFormat.TWELVE_HOUR;
|
||||
|
||||
return `${this.getInviteeStart(inviteeTimeFormat)} - ${this.getInviteeEnd(inviteeTimeFormat)}, ${this.t(
|
||||
this.getInviteeStart("dddd").toLowerCase()
|
||||
)}, ${this.t(this.getInviteeStart("MMMM").toLowerCase())} ${this.getInviteeStart("D, YYYY")}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { TFunction } from "next-i18next";
|
||||
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
import { renderEmail } from "..";
|
||||
import BaseEmail from "./_base-email";
|
||||
|
||||
export default class OrganizerDailyVideoDownloadTranscriptEmail extends BaseEmail {
|
||||
calEvent: CalendarEvent;
|
||||
transcriptDownloadLinks: Array<string>;
|
||||
t: TFunction;
|
||||
|
||||
constructor(calEvent: CalendarEvent, transcriptDownloadLinks: string[]) {
|
||||
super();
|
||||
this.name = "SEND_TRANSCRIPT_DOWNLOAD_LINK";
|
||||
this.calEvent = calEvent;
|
||||
this.transcriptDownloadLinks = transcriptDownloadLinks;
|
||||
this.t = this.calEvent.organizer.language.translate;
|
||||
}
|
||||
protected async getNodeMailerPayload(): Promise<Record<string, unknown>> {
|
||||
return {
|
||||
to: `${this.calEvent.organizer.email}>`,
|
||||
from: `${APP_NAME} <${this.getMailerOptions().from}>`,
|
||||
replyTo: [...this.calEvent.attendees.map(({ email }) => email), this.calEvent.organizer.email],
|
||||
subject: `${this.t("download_transcript_email_subject", {
|
||||
title: this.calEvent.title,
|
||||
date: this.getFormattedDate(),
|
||||
})}`,
|
||||
html: await renderEmail("DailyVideoDownloadTranscriptEmail", {
|
||||
title: this.calEvent.title,
|
||||
date: this.getFormattedDate(),
|
||||
transcriptDownloadLinks: this.transcriptDownloadLinks,
|
||||
language: this.t,
|
||||
name: this.calEvent.organizer.name,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
protected getTimezone(): string {
|
||||
return this.calEvent.organizer.timeZone;
|
||||
}
|
||||
|
||||
protected getOrganizerStart(format: string) {
|
||||
return this.getFormattedRecipientTime({ time: this.calEvent.startTime, format });
|
||||
}
|
||||
|
||||
protected getOrganizerEnd(format: string) {
|
||||
return this.getFormattedRecipientTime({ time: this.calEvent.endTime, format });
|
||||
}
|
||||
|
||||
protected getLocale(): string {
|
||||
return this.calEvent.organizer.language.locale;
|
||||
}
|
||||
|
||||
protected getFormattedDate() {
|
||||
const organizerTimeFormat = this.calEvent.organizer.timeFormat || TimeFormat.TWELVE_HOUR;
|
||||
|
||||
return `${this.getOrganizerStart(organizerTimeFormat)} - ${this.getOrganizerEnd(
|
||||
organizerTimeFormat
|
||||
)}, ${this.t(this.getOrganizerStart("dddd").toLowerCase())}, ${this.t(
|
||||
this.getOrganizerStart("MMMM").toLowerCase()
|
||||
)} ${this.getOrganizerStart("D, YYYY")}`;
|
||||
}
|
||||
}
|
||||
Vendored
+2
@@ -26,6 +26,8 @@ export type VideoApiAdapter =
|
||||
getRecordingDownloadLink?(recordingId: string): Promise<GetAccessLinkResponseSchema>;
|
||||
|
||||
createInstantCalVideoRoom?(endTime: string): Promise<VideoCallData>;
|
||||
|
||||
getAllTranscriptsAccessLinkFromRoomName?(roomName: string): Promise<Array<string>>;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
|
||||
@@ -255,6 +255,7 @@
|
||||
"DAILY_API_KEY",
|
||||
"DAILY_SCALE_PLAN",
|
||||
"DAILY_WEBHOOK_SECRET",
|
||||
"DAILY_MEETING_ENDED_WEBHOOK_SECRET",
|
||||
"DATABASE_DIRECT_URL",
|
||||
"DATABASE_URL",
|
||||
"DEBUG",
|
||||
|
||||
@@ -5177,7 +5177,8 @@ __metadata:
|
||||
"@calcom/tsconfig": "*"
|
||||
"@calcom/types": "*"
|
||||
"@calcom/ui": "*"
|
||||
"@daily-co/daily-js": ^0.37.0
|
||||
"@daily-co/daily-js": ^0.59.0
|
||||
"@daily-co/daily-react": ^0.17.2
|
||||
"@formkit/auto-animate": 1.0.0-beta.5
|
||||
"@glidejs/glide": ^3.5.2
|
||||
"@hookform/error-message": ^2.0.0
|
||||
@@ -5293,6 +5294,7 @@ __metadata:
|
||||
react-timezone-select: ^1.4.0
|
||||
react-turnstile: ^1.1.3
|
||||
react-use-intercom: 1.5.1
|
||||
recoil: ^0.7.7
|
||||
remove-markdown: ^0.5.0
|
||||
rrule: ^2.7.1
|
||||
sanitize-html: ^2.10.0
|
||||
@@ -5813,17 +5815,30 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@daily-co/daily-js@npm:^0.37.0":
|
||||
version: 0.37.0
|
||||
resolution: "@daily-co/daily-js@npm:0.37.0"
|
||||
"@daily-co/daily-js@npm:^0.59.0":
|
||||
version: 0.59.0
|
||||
resolution: "@daily-co/daily-js@npm:0.59.0"
|
||||
dependencies:
|
||||
"@babel/runtime": ^7.12.5
|
||||
"@sentry/browser": ^7.60.1
|
||||
bowser: ^2.8.1
|
||||
dequal: ^2.0.3
|
||||
events: ^3.1.0
|
||||
fast-equals: ^1.6.3
|
||||
lodash: ^4.17.15
|
||||
checksum: 7264f9719b6b3747597d0096113eafea3a8c8ccf368bf967fdbd39a4929188940460510436360898cc870a9153ca950897624ea669fd7dc06070c39cecfc5786
|
||||
checksum: 5891ca13633a7c5c0997c4f4049afc0f67361c2f60a8f9a769d1f2a3bbc60c9d179681f2a3cf2e96598ee2967671ce2f5fef5eb490041912835ee66961f027ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@daily-co/daily-react@npm:^0.17.2":
|
||||
version: 0.17.2
|
||||
resolution: "@daily-co/daily-react@npm:0.17.2"
|
||||
dependencies:
|
||||
fast-deep-equal: ^3.1.3
|
||||
lodash.throttle: ^4.1.1
|
||||
peerDependencies:
|
||||
"@daily-co/daily-js": ">=0.45.0 <1"
|
||||
react: ">=16.13.1"
|
||||
recoil: ^0.7.0
|
||||
checksum: 87f679e6a3f28d2de5dbee4d5d8f97140fa6f3067e13615533dfcfde8d5496a7c48f3f9f916993c36cd90d7854d611ac69ae5243a973664a163c0d534496219c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13111,6 +13126,40 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry-internal/feedback@npm:7.102.1":
|
||||
version: 7.102.1
|
||||
resolution: "@sentry-internal/feedback@npm:7.102.1"
|
||||
dependencies:
|
||||
"@sentry/core": 7.102.1
|
||||
"@sentry/types": 7.102.1
|
||||
"@sentry/utils": 7.102.1
|
||||
checksum: 88a8649d08c80604cc4efc61b3b55b212be76ee8340f4de05db43b29ba86db72c138784640786dcef68ddd0bec1e3fcc1b421134bfb5345ed8fa177a953c7597
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry-internal/replay-canvas@npm:7.102.1":
|
||||
version: 7.102.1
|
||||
resolution: "@sentry-internal/replay-canvas@npm:7.102.1"
|
||||
dependencies:
|
||||
"@sentry/core": 7.102.1
|
||||
"@sentry/replay": 7.102.1
|
||||
"@sentry/types": 7.102.1
|
||||
"@sentry/utils": 7.102.1
|
||||
checksum: f5b0dd73a5760663f9fdeee098e2c3d5e9c77b099825e03008bcebd722c432b642b30cbf4727ad432086f3a319b7bce8ac9e8c1f25809371c8297044355edf5f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry-internal/tracing@npm:7.102.1":
|
||||
version: 7.102.1
|
||||
resolution: "@sentry-internal/tracing@npm:7.102.1"
|
||||
dependencies:
|
||||
"@sentry/core": 7.102.1
|
||||
"@sentry/types": 7.102.1
|
||||
"@sentry/utils": 7.102.1
|
||||
checksum: 7e7caa5864cfdc91187b1da38a402cc342c63697714e83885bb6fe179edf7ea91704d17783ad6774eac206813000ca25798e5a1d2592dfb8833052f91492f90a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry-internal/tracing@npm:7.107.0":
|
||||
version: 7.107.0
|
||||
resolution: "@sentry-internal/tracing@npm:7.107.0"
|
||||
@@ -13146,6 +13195,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/browser@npm:^7.60.1":
|
||||
version: 7.102.1
|
||||
resolution: "@sentry/browser@npm:7.102.1"
|
||||
dependencies:
|
||||
"@sentry-internal/feedback": 7.102.1
|
||||
"@sentry-internal/replay-canvas": 7.102.1
|
||||
"@sentry-internal/tracing": 7.102.1
|
||||
"@sentry/core": 7.102.1
|
||||
"@sentry/replay": 7.102.1
|
||||
"@sentry/types": 7.102.1
|
||||
"@sentry/utils": 7.102.1
|
||||
checksum: 53908bd2c209f18b24452d10020d2a37f5300d5a24c6f092acd13baf6f5e2fe15b6c344289706fbf13c442335f8452c7043f4353aba2a940535a7873a224cc2a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/cli@npm:^1.74.6":
|
||||
version: 1.75.2
|
||||
resolution: "@sentry/cli@npm:1.75.2"
|
||||
@@ -13162,6 +13226,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/core@npm:7.102.1":
|
||||
version: 7.102.1
|
||||
resolution: "@sentry/core@npm:7.102.1"
|
||||
dependencies:
|
||||
"@sentry/types": 7.102.1
|
||||
"@sentry/utils": 7.102.1
|
||||
checksum: 4617cb4845499b52f36d7d212f38f306e68ca2a7a5645519491f55f66a6a9f422403c74a9d902ff5c8c241fd25e9c3187b14705e738bb823d340aa3dc6a2b273
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/core@npm:7.107.0":
|
||||
version: 7.107.0
|
||||
resolution: "@sentry/core@npm:7.107.0"
|
||||
@@ -13261,6 +13335,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/replay@npm:7.102.1":
|
||||
version: 7.102.1
|
||||
resolution: "@sentry/replay@npm:7.102.1"
|
||||
dependencies:
|
||||
"@sentry-internal/tracing": 7.102.1
|
||||
"@sentry/core": 7.102.1
|
||||
"@sentry/types": 7.102.1
|
||||
"@sentry/utils": 7.102.1
|
||||
checksum: 99ba652f8d0d0654cabda485339c1b4960dc9015835ba106582e6c3aefcd2544e4f0081fd28611fe1df939a4e8c3c2f672e195c294407e52c31bd24fd88db9bb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/replay@npm:7.77.0":
|
||||
version: 7.77.0
|
||||
resolution: "@sentry/replay@npm:7.77.0"
|
||||
@@ -13282,6 +13368,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/types@npm:7.102.1":
|
||||
version: 7.102.1
|
||||
resolution: "@sentry/types@npm:7.102.1"
|
||||
checksum: e98e6e793d1d13e1bece57bd732a0606acdf23533aa3f3a5cbbb39c7359afef52f05848eb5ab785306e15846e29e7219e0fe63fc26798bda5224263162dea9d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/types@npm:7.107.0":
|
||||
version: 7.107.0
|
||||
resolution: "@sentry/types@npm:7.107.0"
|
||||
@@ -13296,6 +13389,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/utils@npm:7.102.1":
|
||||
version: 7.102.1
|
||||
resolution: "@sentry/utils@npm:7.102.1"
|
||||
dependencies:
|
||||
"@sentry/types": 7.102.1
|
||||
checksum: 95a4ad8fbe75e2afbeb4cb14ea5f465015b7d0c5b200a0e2f91e2944940c797c20dea534cb57f93fb164d313860437466bd247a2b38996ba6160c6d672d2d99a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sentry/utils@npm:7.107.0":
|
||||
version: 7.107.0
|
||||
resolution: "@sentry/utils@npm:7.107.0"
|
||||
@@ -17157,6 +17259,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vercel/analytics@npm:^0.1.6":
|
||||
version: 0.1.11
|
||||
resolution: "@vercel/analytics@npm:0.1.11"
|
||||
peerDependencies:
|
||||
react: ^16.8||^17||^18
|
||||
checksum: 05b8180ac6e23ebe7c09d74c43f8ee78c408cd0b6546e676389cbf4fba44dfeeae3648c9b52e2421be64fe3aeee8b026e6ea4bdfc0589fb5780670f2b090a167
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vercel/edge-config@npm:^0.1.1":
|
||||
version: 0.1.1
|
||||
resolution: "@vercel/edge-config@npm:0.1.1"
|
||||
@@ -20058,6 +20169,7 @@ __metadata:
|
||||
resolution: "calcom-monorepo@workspace:."
|
||||
dependencies:
|
||||
"@changesets/cli": ^2.26.1
|
||||
"@daily-co/daily-js": ^0.59.0
|
||||
"@deploysentinel/playwright": ^0.3.3
|
||||
"@playwright/test": ^1.31.2
|
||||
"@snaplet/copycat": ^4.1.0
|
||||
@@ -20066,6 +20178,7 @@ __metadata:
|
||||
"@types/jsonwebtoken": ^9.0.3
|
||||
c8: ^7.13.0
|
||||
checkly: latest
|
||||
city-timezones: ^1.2.1
|
||||
dotenv-checker: ^1.1.5
|
||||
eslint: ^8.34.0
|
||||
husky: ^8.0.0
|
||||
@@ -25467,13 +25580,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-equals@npm:^1.6.3":
|
||||
version: 1.6.3
|
||||
resolution: "fast-equals@npm:1.6.3"
|
||||
checksum: d2de5af5e927cefbc7049e846226aa9ca71bdbae3e179a40774e2853c65e00d78158250af3635e7b3b8662ed7c0796d405962d8be3d1b87beb109668a46568ed
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-equals@npm:^2.0.3":
|
||||
version: 2.0.4
|
||||
resolution: "fast-equals@npm:2.0.4"
|
||||
@@ -27612,6 +27718,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hamt_plus@npm:1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "hamt_plus@npm:1.0.2"
|
||||
checksum: af26ea32db03009019cc83dfa9411521a2fa16079443de1a502c9be46d8b3c975acda8ed93fc5750ef08d3186d35901e2d8cfe717dd54bea67b358601fa74e4c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"handlebars@npm:^4.7.7":
|
||||
version: 4.7.7
|
||||
resolution: "handlebars@npm:4.7.7"
|
||||
@@ -30107,6 +30220,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isomorphic-ws@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "isomorphic-ws@npm:5.0.0"
|
||||
peerDependencies:
|
||||
ws: "*"
|
||||
checksum: e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isstream@npm:~0.1.2":
|
||||
version: 0.1.2
|
||||
resolution: "isstream@npm:0.1.2"
|
||||
@@ -32556,6 +32678,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.throttle@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "lodash.throttle@npm:4.1.1"
|
||||
checksum: 129c0a28cee48b348aef146f638ef8a8b197944d4e9ec26c1890c19d9bf5a5690fe11b655c77a4551268819b32d27f4206343e30c78961f60b561b8608c8c805
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash.union@npm:^4.6.0":
|
||||
version: 4.6.0
|
||||
resolution: "lodash.union@npm:4.6.0"
|
||||
@@ -40155,6 +40284,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"recoil@npm:^0.7.7":
|
||||
version: 0.7.7
|
||||
resolution: "recoil@npm:0.7.7"
|
||||
dependencies:
|
||||
hamt_plus: 1.0.2
|
||||
peerDependencies:
|
||||
react: ">=16.13.1"
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
checksum: 65edecbcb8d2cde89bfd61ec679c200483472a6cd343c33e4e9142b6ce524fb17d1fecc2bfd8c392926aaa8178c81457f165b32abce2a9662f51f98822c0a9cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"redent@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "redent@npm:3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user