-
Notifications
You must be signed in to change notification settings - Fork 11.7k
feat: cal.ai self serve #21827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
feat: cal.ai self serve #21827
Changes from 10 commits
Commits
Show all changes
82 commits
Select commit
Hold shift + click to select a range
5cac626
feat: cal.ai self serve
Udit-takkar e38f713
chore: progress
Udit-takkar f52c497
Merge branch 'main' into feat/cal-ai-self-serve
Udit-takkar b6be1fd
fix: form
Udit-takkar e7a61c4
fix: links[endpoint] error
Udit-takkar d2ad1cf
chore: form
Udit-takkar ca041da
Merge branch 'main' into feat/cal-ai-self-serve
Udit-takkar 049c1bb
Merge branch 'main' into feat/cal-ai-self-serve
Udit-takkar 3b9cf80
feat: finish setup
Udit-takkar def1186
chore: fix
Udit-takkar 8a5967b
fix: trpc hang bug
Udit-takkar b1c0524
Merge branch 'main' into feat/cal-ai-self-serve
Udit-takkar 436301c
chore: save progress
Udit-takkar e9c3c05
Merge branch 'main' into feat/cal-ai-self-serve
Udit-takkar 254a2e0
feat: add phone number billing
Udit-takkar 807d515
feat: retell ai webhook
Udit-takkar 6771fa7
Merge branch 'main' into feat/cal-ai-self-serve
Udit-takkar 39cd886
fix: type error
Udit-takkar a27a800
feat: remove api key input
Udit-takkar 9676ef3
feat: add delete
Udit-takkar fb852c2
feat: add delete logic and assign phone number
Udit-takkar 6d06d74
refactor: use design pattern
Udit-takkar 7106fca
chore: remove comment
Udit-takkar bf7b9c9
Merge branch 'main' into feat/cal-ai-self-serve
Udit-takkar 610bd75
fix: type errror
Udit-takkar 95f82aa
refactor: decouple retell ai
Udit-takkar b79c4b5
fix: name
Udit-takkar 130e38c
chore: comment unued
Udit-takkar 7ff4667
fix: type errors
Udit-takkar d29eeb9
fix: type error
Udit-takkar e6700d2
feat: import phone number functionality
Udit-takkar 47d4ad7
Merge branch 'main' into feat/cal-ai-self-serve
Udit-takkar cdaeae4
feat: wwebhook handler
Udit-takkar 8791002
fix: type errors
Udit-takkar d9baa63
fix: type error
Udit-takkar 443fa77
fix: 404 new trpc endpoint bug
Udit-takkar 82cca61
fix: impor
Udit-takkar b1b9aac
chore
Udit-takkar 9be39b8
feat: new version (wip)
Udit-takkar 0754d7b
chore: improvements
Udit-takkar 2701675
tests: add unit tests
Udit-takkar 470f034
fix: types
Udit-takkar 20bcd41
feat: new desing
Udit-takkar 0b9f2b8
refactor: move everything to service
Udit-takkar 351c4db
tests: add unit tests for service and client
Udit-takkar 0ad600a
fix: deleting cal ai action and workflow
Udit-takkar b0d50a7
chore: update
Udit-takkar e7a804d
refactor: improve code
Udit-takkar 9b9ef44
chore: use new app route
Udit-takkar 326621a
fix: infinite rendering bug
Udit-takkar feb6236
feat: add team support completely
Udit-takkar 1df875e
chore: remove old design and unused
Udit-takkar 3146566
perf: improve agent repository query
Udit-takkar f50049e
perf: improve query
Udit-takkar faebeb1
chore: improvements and rate limiting
Udit-takkar 4c62e65
fix: credit reposiotory
Udit-takkar 8d8ff2d
fix: update unit tests and type error
Udit-takkar 62bbd3f
fix: type
Udit-takkar f9886a2
chore: types
Udit-takkar 5253927
fix: use types from retell sdk
Udit-takkar 9301519
chore: remove PhoneData
Udit-takkar 81f0145
chore: code improvements
Udit-takkar e97d2ed
chore: code improvements
Udit-takkar c1791b8
fix: tests and types
Udit-takkar b34588e
chore: improve UI
Udit-takkar a34a25e
chore: type error
Udit-takkar fe7ec30
fix: improvements and i18n
Udit-takkar 69f1ec1
chore: remove unused
Udit-takkar c153c8b
chore: more i18n
Udit-takkar b9c2de2
chore: i18n
Udit-takkar fcfb4f6
fix: type err
Udit-takkar 13ba93a
chore: missing check
Udit-takkar 990c9c8
chore: update docs
Udit-takkar 85878ec
nit
Udit-takkar 8d0d762
chore: i18n, other improvements
Udit-takkar 265e536
fix: formatting
Udit-takkar 70c18b8
fix: type
Udit-takkar 953001f
refactor: stripe related webhooks
Udit-takkar 49bd22a
Merge branch 'main' into feat/cal-ai-self-serve
emrysal 2cdbfa7
chore: imporovements
Udit-takkar 2f52003
fix: types
Udit-takkar 15ddc3d
chore
Udit-takkar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/my-account/phone-numbers/page.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { _generateMetadata } from "app/_utils"; | ||
| import { unstable_cache } from "next/cache"; | ||
| import { revalidatePath } from "next/cache"; | ||
| import { cookies, headers } from "next/headers"; | ||
| import { redirect } from "next/navigation"; | ||
|
|
||
| import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; | ||
| import { PhoneNumberRepository } from "@calcom/lib/server/repository/phoneNumber"; | ||
|
|
||
| import { buildLegacyRequest } from "@lib/buildLegacyCtx"; | ||
|
|
||
| import PhoneNumbersQueryView from "~/settings/my-account/phone-numbers-view"; | ||
|
|
||
| export const generateMetadata = async () => | ||
| await _generateMetadata( | ||
| (t) => t("cal_ai_phone_numbers"), | ||
| (t) => t("cal_ai_phone_numbers_description"), | ||
| undefined, | ||
| undefined, | ||
| "/settings/my-account/phone-numbers" | ||
| ); | ||
|
|
||
| const getCachedPhoneNumbers = unstable_cache( | ||
| async (userId: number) => { | ||
| return await PhoneNumberRepository.findPhoneNumbersFromUserId({ userId }); | ||
| }, | ||
| undefined, | ||
| { revalidate: 3600, tags: ["viewer.phoneNumbers.list"] } // Cache for 1 hour | ||
| ); | ||
|
|
||
| const Page = async () => { | ||
| const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) }); | ||
|
|
||
| if (!session) { | ||
| redirect("/auth/login?callbackUrl=/settings/my-account/phone-numbers"); | ||
| } | ||
|
|
||
| const userId = session.user.id; | ||
| const revalidatePage = async () => { | ||
| "use server"; | ||
| revalidatePath("settings/my-account/phone-numbers"); | ||
| }; | ||
|
|
||
| const cachedNumbers = await getCachedPhoneNumbers(userId); | ||
|
|
||
| return <PhoneNumbersQueryView revalidatePage={revalidatePage} cachedNumbers={cachedNumbers} />; | ||
| }; | ||
|
|
||
| export default Page; |
133 changes: 133 additions & 0 deletions
133
apps/web/modules/settings/my-account/phone-numbers-view.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| "use client"; | ||
|
|
||
| import { useState } from "react"; | ||
|
|
||
| import { Dialog } from "@calcom/features/components/controlled-dialog"; | ||
| import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; | ||
| import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
| import type { RouterOutputs } from "@calcom/trpc/react"; | ||
| import { trpc } from "@calcom/trpc/react"; | ||
| import { Button } from "@calcom/ui/components/button"; | ||
| import { DialogContent } from "@calcom/ui/components/dialog"; | ||
| import { DialogFooter } from "@calcom/ui/components/dialog"; | ||
| import { EmptyScreen } from "@calcom/ui/components/empty-screen"; | ||
| import { Icon } from "@calcom/ui/components/icon"; | ||
| import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton"; | ||
| import { showToast } from "@calcom/ui/components/toast"; | ||
|
|
||
| const SkeletonLoader = () => { | ||
| return ( | ||
| <SkeletonContainer> | ||
| <div className="border-subtle space-y-6 rounded-b-xl border border-t-0 px-4 py-8 sm:px-6"> | ||
| <SkeletonText className="h-8 w-full" /> | ||
| <SkeletonText className="h-8 w-full" /> | ||
| <SkeletonText className="h-8 w-full" /> | ||
| <SkeletonButton className="ml-auto h-8 w-20 rounded-md p-5" /> | ||
| </div> | ||
| </SkeletonContainer> | ||
| ); | ||
| }; | ||
|
|
||
| function PhoneNumbersView({ | ||
| numbers = [], | ||
| revalidatePage, | ||
| }: { | ||
| numbers?: RouterOutputs["viewer"]["phoneNumbers"]["list"]; | ||
| revalidatePage: () => Promise<void>; | ||
| }) { | ||
| const { t } = useLocale(); | ||
| const [isBuyDialogOpen, setIsBuyDialogOpen] = useState(false); | ||
| const utils = trpc.useContext(); | ||
|
|
||
| const buyNumberMutation = trpc.viewer.phoneNumbers.buy.useMutation({ | ||
| onSuccess: async () => { | ||
| showToast(t("phone_number_purchased_successfully"), "success"); | ||
| await utils.viewer.phoneNumbers.list.invalidate(); | ||
| await utils.viewer.me.invalidate(); | ||
| await revalidatePage(); | ||
| setIsBuyDialogOpen(false); | ||
| }, | ||
| onError: (error) => { | ||
| console.log("error", error); | ||
| showToast(error.message, "error"); | ||
| }, | ||
| }); | ||
|
|
||
| const BuyNumberButton = (props: React.ComponentProps<typeof Button>) => ( | ||
| <Button {...props} color="secondary" StartIcon={Icon.Plus} onClick={() => setIsBuyDialogOpen(true)}> | ||
| {t("buy_number")} | ||
| </Button> | ||
| ); | ||
|
|
||
| return ( | ||
| <> | ||
| <SettingsHeader | ||
| title={t("cal_ai_phone_numbers")} | ||
| description={t("cal_ai_phone_numbers_description")} | ||
| CTA={<BuyNumberButton />} | ||
| borderInShellHeader={true}> | ||
| <div> | ||
| {numbers.length > 0 ? ( | ||
| <div className="border-subtle rounded-b-lg border border-t-0"> | ||
| {numbers.map((number, index) => ( | ||
| <div | ||
| key={number.id} | ||
| className={`flex items-center justify-between p-6 ${ | ||
| numbers.length !== index + 1 ? "border-subtle border-b" : "" | ||
| }`}> | ||
| <div className="flex items-center"> | ||
| <Icon.Phone className="h-6 w-6 text-gray-400" /> | ||
| <span className="text-emphasis ml-4 text-sm font-medium">{number.phoneNumber}</span> | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| ) : ( | ||
| <EmptyScreen | ||
| Icon={Icon.Phone} | ||
| headline={t("no_phone_numbers_yet")} | ||
| description={t("buy_your_first_phone_number")} | ||
| className="rounded-b-lg rounded-t-none border-t-0" | ||
| buttonRaw={<BuyNumberButton />} | ||
| /> | ||
| )} | ||
| </div> | ||
| </SettingsHeader> | ||
| <Dialog open={isBuyDialogOpen} onOpenChange={setIsBuyDialogOpen}> | ||
| <DialogContent type="creation"> | ||
| <div className="flex flex-col"> | ||
| <div className="mb-4"> | ||
| <h3 className="text-emphasis text-lg font-bold">{t("buy_new_number")}</h3> | ||
| <p className="text-default text-sm">{t("buy_number_cost_50_credits")}</p> | ||
| </div> | ||
| <DialogFooter showDivider className="relative"> | ||
| <Button onClick={() => setIsBuyDialogOpen(false)} color="secondary"> | ||
| {t("cancel")} | ||
| </Button> | ||
| <Button onClick={() => buyNumberMutation.mutate()} loading={buyNumberMutation.isPending}> | ||
| {t("buy_number_with_credits")} | ||
| </Button> | ||
| </DialogFooter> | ||
| </div> | ||
| </DialogContent> | ||
| </Dialog> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default function PhoneNumbersQueryView({ | ||
| cachedNumbers, | ||
| revalidatePage, | ||
| }: { | ||
| cachedNumbers: RouterOutputs["viewer"]["phoneNumbers"]["list"]; | ||
| revalidatePage: () => Promise<void>; | ||
| }) { | ||
| const { t } = useLocale(); | ||
| const { data: numbers, isPending } = trpc.viewer.phoneNumbers.list.useQuery(undefined, { | ||
| suspense: false, | ||
| }); | ||
|
|
||
| if (isPending && !cachedNumbers) return <SkeletonLoader />; | ||
|
|
||
| return <PhoneNumbersView numbers={numbers || cachedNumbers} revalidatePage={revalidatePage} />; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
packages/features/ee/cal-ai-phone/handleCreateSelfServePhoneCall.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { createSelfServePhoneCall } from "@calcom/features/ee/cal-ai-phone/retellAIService"; | ||
| import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; | ||
| import { prisma } from "@calcom/prisma"; | ||
|
|
||
| import { TRPCError } from "@trpc/server"; | ||
|
|
||
| export const handleCreateSelfServePhoneCall = async ({ | ||
| userId, | ||
| eventTypeId, | ||
| numberToCall, | ||
| }: { | ||
| userId: number; | ||
| eventTypeId: number; | ||
| numberToCall: string; | ||
| }) => { | ||
| await checkRateLimitAndThrowError({ | ||
| rateLimitingType: "core", | ||
| identifier: `create-self-serve-phone-call:${userId}`, | ||
| }); | ||
|
|
||
| const config = await prisma.aISelfServeConfiguration.findFirst({ | ||
| where: { | ||
| eventTypeId: eventTypeId, | ||
| eventType: { | ||
| userId: userId, | ||
| }, | ||
| }, | ||
| include: { | ||
| yourPhoneNumber: true, | ||
| }, | ||
| }); | ||
|
|
||
| console.log("config", config); | ||
| if (!config || !config.yourPhoneNumber) { | ||
| throw new TRPCError({ | ||
| code: "BAD_REQUEST", | ||
| message: "AI not configured for this event type or no phone number is assigned.", | ||
| }); | ||
| } | ||
|
|
||
| const fromNumber = config.yourPhoneNumber.phoneNumber; | ||
| const call = await createSelfServePhoneCall(fromNumber, numberToCall); | ||
|
|
||
| return call; | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: Create smaller PRs