Build and Deploy a SaaS AI Agent Platform | Next.js 15, React, Better Auth, Polar | Part 2/2
By Code With Antonio
Key Concepts
- AI Agents: AI personas with custom instructions that can join video calls.
- Searchable Transcripts: Textual representations of meeting conversations that can be searched for specific keywords.
- AI Chat: An AI-powered chat functionality that understands the full meeting context and can answer questions.
- Subscriptions and Free Trials: Implementation of payment plans and trial periods using Polar.sh.
- Deployment: The process of making the application live and accessible to users.
- Stream Chat: A service used for implementing real-time chat functionality.
- Polar.sh: A platform used for managing subscriptions and payments.
- Drizzle ORM: An Object-Relational Mapper used for interacting with the database.
- Injest: A service used for background job processing.
- Vercel: A platform used for deploying web applications.
Transcript and Chat Implementation
Adding Transcript Functionality
getTranscript
Procedure:- A protected procedure is created to fetch the meeting transcript.
- It accepts the meeting ID as input.
- It retrieves the meeting details from the database, ensuring the logged-in user has access.
- If no transcript URL exists, it returns an empty array.
- It fetches the transcript from the URL, parses it using
jsonl-parse-stringify
, and converts it into a structured format. - It populates speaker information (users and agents) by fetching their details from the database.
- It maps the transcript items to include speaker information.
- Finally, it returns the transcript with speaker details.
- Transcript Component (
transcript.tsx
):- The component is created to display the transcript.
- It uses
react-highlight-words
for highlighting search terms. - It uses
useQuery
fromtanstack-react-query
to fetch the transcript data. - It includes a search input field to filter the transcript.
- It iterates over the filtered transcript data and renders each item with the speaker's avatar, name, timestamp, and text.
- The
react-highlight-words
component highlights the search terms within the transcript text. - Example: Searching for "math" highlights all occurrences of "math" in the transcript.
- Dependencies:
highlight-react
highlight-words
@types/react-highlight-words
- Code Snippets:
- Parsing transcript from URL:
const transcript = await fetch(existingMeeting.transcriptUrl).then(res => res.text()).then(text => jsonlParse(text, { type: streamTranscriptionItem }));
- Highlighting search terms:
<Highlighter highlightClassName="highlight" searchWords={[searchQuery]} autoEscape={true} textToHighlight={item.text} />
- Parsing transcript from URL:
Implementing AI Chat Functionality
- Stream Chat Integration:
- The
stream-chat
package is installed. - A
stream-chat.ts
file is created in thelib
directory to manage Stream Chat initialization. - Environment variables for Stream Chat API key and secret are defined.
- A
generateChatToken
procedure is created to generate a token for authenticating users with Stream Chat.
- The
- Chat Provider Component (
chat-provider.tsx
):- This component acts as a wrapper for the chat UI.
- It fetches the currently logged-in user's session.
- It renders the
ChatUI
component, passing in the meeting ID, meeting name, user ID, username, and user image.
- Chat UI Component (
chat-ui.tsx
):- This component displays the chat interface.
- It uses
useCreateChatClient
fromstream-chat-react
to initialize the Stream Chat client. - It uses
useMutation
to call thegenerateChatToken
procedure and obtain a chat token. - It uses
useEffect
to connect to the chat channel when the client is initialized. - It renders the chat interface using components from
stream-chat-react
:Chat
,Channel
,Window
,MessageList
,MessageInput
, andThread
.
- Dependencies:
stream-chat
stream-chat-react
- Code Snippets:
- Initializing Stream Chat client:
const client = useCreateChatClient({ apiKey: process.env.NEXT_PUBLIC_STREAM_CHAT_API_KEY, generateToken: async () => { const { token } = await mutateAsync(); return token; }, });
- Connecting to the chat channel:
useEffect(() => { if (!client) return; const channel = client.channel('messaging', meetingId, { members: [userId], }); setChannel(channel); }, [client, meetingId, userId]);
- Initializing Stream Chat client:
Integrating AI Agent Responses
- Webhook Implementation:
- The Stream Chat webhook is used to listen for new messages.
- When a new message is received, the webhook checks if the sender is not an agent.
- If the sender is a user, the webhook retrieves the meeting summary and agent instructions.
- It fetches the previous five messages from the chat to provide context to the AI agent.
- It uses the OpenAI API to generate a response based on the meeting summary, agent instructions, and previous messages.
- The AI agent's response is then sent to the chat channel.
- OpenAI Integration:
- The
openai
package is installed. - The OpenAI API is used to generate responses based on the meeting summary, agent instructions, and previous messages.
- The
chatCompletions.create
method is used to generate the AI agent's response.
- The
- Code Snippets:
- Generating GPT response:
const gptResponse = await openai.chat.completions.create({ messages: [ { role: 'system', content: instructions }, ...previousMessages, { role: 'user', content: text }, ], model: 'gpt-3.5-turbo', });
- Sending message to Stream Chat:
await channel.sendMessage({ text: gptResponseText, user: { id: existingAgent.id, name: existingAgent.name, image: generateAvatarUri({ seed: existingAgent.name }), }, });
- Generating GPT response:
Implementing Payments with Polar.sh
Setting up Polar.sh
- Account Creation:
- Create accounts on both
polar.sh
(production) andsandbox.polar.sh
(development). - The sandbox environment is used for testing purposes.
- Create accounts on both
- API Token Generation:
- Generate a new API token in the sandbox environment with all scopes enabled and no expiration date.
- Store the token in the environment variables as
POLAR_ACCESS_TOKEN
.
- Polar.sh SDK Integration:
- Install the necessary Polar.sh packages:
@polar.sh/sdk
and@polar.sh/better-out
. - Create a
polar.ts
file in thelib
directory to initialize the Polar.sh client. - Configure the Polar.sh client with the API token and set the server type to
sandbox
.
- Install the necessary Polar.sh packages:
- Better Out Plugin Integration:
- Add the Polar.sh plugin to the Better Out configuration in
al/util.ts
. - Enable
createCustomerOnSignUp
to automatically create a Polar.sh customer when a new user signs up. - Use the
checkout
plugin to restrict checkout to authenticated users and redirect to/upgrade
on successful purchase. - Use the
portal
plugin to enable access to the customer portal.
- Add the Polar.sh plugin to the Better Out configuration in
- Code Snippets:
- Initializing Polar.sh client:
import { Polar } from '@polar.sh/sdk'; export const polarClient = new Polar({ accessToken: process.env.POLAR_ACCESS_TOKEN, server: 'sandbox', });
- Adding Polar.sh plugin to Better Out:
plugins: [ polar({ client: polarClient, createCustomerOnSignUp: true, use: [ checkout({ authenticated: true, successUrl: '/upgrade', }), portal(), ], }), ],
- Initializing Polar.sh client:
Implementing Free Tier Usage
- Premium Router:
- Create a
premium
module with a server and procedures. - Create a
getFreeUsage
procedure to fetch the user's free tier usage. - The procedure retrieves the Polar.sh customer by external ID (user ID).
- It checks if the customer has any active subscriptions. If so, it doesn't show free usage.
- It counts the number of meetings and agents created by the user.
- It returns the meeting count and agent count to the client.
- Create a
- Dashboard Trial Component (
dashboard-trial.tsx
):- This component displays the user's free tier usage in the dashboard.
- It uses
useQuery
to fetch the free usage data from thegetFreeUsage
procedure. - It displays the number of meetings and agents used, along with progress bars.
- It includes a link to the
/upgrade
page.
- Premium Constants:
- Create a
constants.ts
file in thepremium
module to define the maximum number of free meetings and agents.
- Create a
- Premium Procedure:
- Create a
premiumProcedure
intrpc/init.ts
to enforce free tier limits. - This procedure extends the protected procedure and checks if the user has reached the free meeting or agent limit.
- If the limit is reached, it throws a forbidden error.
- Create a
- Code Snippets:
- Fetching free usage data:
const { data } = useQuery({ queryKey: ['premium.getFreeUsage'], queryFn: () => trpc.premium.getFreeUsage.query(), });
- Premium procedure implementation:
const premiumProcedure = (entity: 'meetings' | 'agents') => protectedProcedure.use(async ({ ctx, next }) => { // ... (code to check free tier limits) if (shouldThrowMeetingError) { throw new TRPCError({ code: 'FORBIDDEN', message: 'You have reached the maximum number of free meetings.', }); } // ... (similar code for agent error) return next({ ctx: { customer, }, }); });
- Fetching free usage data:
Implementing the Upgrade Page
- Premium Router Procedures:
- Add
getProducts
procedure to fetch all non-archived and recurring products from Polar.sh. - Add
getCurrentSubscription
procedure to fetch the user's current subscription from Polar.sh.
- Add
- Upgrade Page (
upgrade/page.tsx
):- This page displays the available subscription plans.
- It uses
prefetchQuery
to prefetch the products and current subscription data on the server. - It uses
useSuspenseQuery
to fetch the products and current subscription data on the client. - It renders the
UpgradeView
component.
- Upgrade View Component (
upgrade-view.tsx
):- This component displays the available subscription plans.
- It iterates over the products and renders a
PricingCard
component for each product. - It checks if the user is already subscribed to a product and displays the appropriate button text ("Manage" or "Change Plan").
- It uses the
checkout
method from the Better Out plugin to open the Polar.sh checkout page.
- Pricing Card Component (
pricing-card.tsx
):- This component displays the details of a subscription plan.
- It uses
class-variance-authority
to define different variants for the card (default and highlighted). - It displays the product title, description, price, features, and a call-to-action button.
- Code Snippets:
- Fetching products and current subscription:
const { data: products } = useSuspenseQuery({ queryKey: ['premium.getProducts'], queryFn: () => trpc.premium.getProducts.query(), }); const { data: currentSubscription } = useSuspenseQuery({ queryKey: ['premium.getCurrentSubscription'], queryFn: () => trpc.premium.getCurrentSubscription.query(), });
- Rendering the pricing card:
<PricingCard key={product.id} variant={product.metadata?.variant as 'default' | 'highlighted' || 'default'} title={product.name} description={product.description} price={price} priceSuffix={`/${product.prices[0].recurring_interval}`} features={product.benefits.map((benefit) => benefit.description)} badge={product.metadata?.badge} buttonText={buttonText} onClick={onClick} />
- Fetching products and current subscription:
Deployment and Bug Fixes
Preparing for Deployment
- Dashboard Command:
- Implement the dashboard command to search for meetings and agents.
- Use
useRouter
to navigate to the selected meeting or agent. - Use
useQuery
to fetch meetings and agents.
- Meeting Count:
- Implement the proper meeting count for agents using
database.$count
. - Update the
getMany
andgetOne
procedures to include the meeting count.
- Implement the proper meeting count for agents using
- Cancel Meeting:
- Remove the cancel meeting functionality as it doesn't provide much value to the user.
- Build Test:
- Run
npm run build
to check for any errors before deployment.
- Run
Deployment Process
- Vercel Integration:
- Add the project to Vercel and connect it with the GitHub repository.
- Add the environment variables to the Vercel project.
- Deploy the project to Vercel.
- Stream Configuration:
- Update the Stream webhook URL to the new Vercel URL.
- Better Out URL:
- Update the Better Out URL in the environment variables to the new Vercel URL.
- Authentication Configuration:
- Update the GitHub and Google authentication settings with the new Vercel URL.
- Injest Integration:
- Create an account with Injest and connect it with the Vercel project.
- Enable Injest signing key and Injest event key.
- Configure deployment protection bypass for Injest.
- Redirect from Homepage:
- Add a redirect from the homepage to
/meetings
innext.config.js
.
- Add a redirect from the homepage to
Bug Fixes
- Injest Fetch Error:
- If the Injest
step.fetch
is failing, replace it with a normalfetch
method. - Remove the
response.ext.text
transformation in the next step.
- If the Injest
- NPM Install Error:
- If there are NPM install errors during deployment, override the install command with
npm install --legacy-peer-deps
.
- If there are NPM install errors during deployment, override the install command with
Conclusion
The YouTube video tutorial provides a comprehensive guide to building a real-time AI video call application with various advanced features. It covers everything from setting up authentication and database connections to implementing AI agents, searchable transcripts, AI chat, subscriptions, and deployment. The tutorial emphasizes the use of modern technologies and best practices, providing viewers with the knowledge and skills to create a sophisticated and feature-rich application. The detailed explanations, code snippets, and troubleshooting tips make it a valuable resource for developers looking to build similar applications.
Chat with this Video
AI-PoweredHi! I can answer questions about this video "Build and Deploy a SaaS AI Agent Platform | Next.js 15, React, Better Auth, Polar | Part 2/2". What would you like to know?