diff --git a/examples/meeting_bot/README.md b/examples/meeting_bot/README.md new file mode 100644 index 000000000..ec93cdb1f --- /dev/null +++ b/examples/meeting_bot/README.md @@ -0,0 +1,97 @@ +# Codegen Meeting Bot Examples + +This directory contains proof-of-concept examples for implementing a Codegen bot that can join Google Meet and Zoom meetings. + +## Overview + +The Codegen Meeting Bot is designed to: + +1. Join video meetings on platforms like Google Meet and Zoom +1. Record and transcribe meetings +1. Answer questions about Codegen during meetings +1. Generate meeting summaries and action items + +## Implementation Options + +### 1. Recall.ai Integration (recall_ai_poc.py) + +This example demonstrates how to use the [Recall.ai](https://www.recall.ai/) API to create a meeting bot that works across multiple platforms. + +**Features:** + +- Platform-agnostic implementation (works with Google Meet, Zoom, MS Teams, etc.) +- Real-time transcription streaming +- Meeting recording and processing +- Customizable bot name and appearance + +**Requirements:** + +- Python 3.8+ +- Recall.ai API key +- Required packages: `requests`, `asyncio`, `websockets` + +**Usage:** + +```python +# Set your Recall.ai API key +export RECALL_API_KEY="your_api_key_here" + +# Run the example +python recall_ai_poc.py +``` + +### 2. Zoom SDK Integration (zoom_sdk_poc.js) + +This example demonstrates how to use the Zoom Meeting SDK to create a bot specifically for Zoom meetings. + +**Features:** + +- Join Zoom meetings programmatically +- Listen to meeting events (chat messages, participant changes, etc.) +- Respond to questions in the chat +- Customizable bot behavior + +**Requirements:** + +- Node.js +- Zoom Meeting SDK credentials +- Required packages: `puppeteer`, `express`, `body-parser`, `crypto`, `cors` + +**Usage:** + +```javascript +// Set your Zoom SDK credentials +export ZOOM_SDK_KEY="your_sdk_key_here" +export ZOOM_SDK_SECRET="your_sdk_secret_here" + +// Install dependencies +npm install puppeteer express body-parser crypto cors + +// Run the example +node zoom_sdk_poc.js +``` + +## Integration with Codegen + +To fully integrate these examples with Codegen, you would need to: + +1. Connect to the Codegen API to process questions and generate responses +1. Implement a calendar integration to automatically schedule bots for meetings +1. Create a user interface for managing bot settings and viewing meeting summaries +1. Set up secure storage for meeting recordings and transcripts + +## Next Steps + +These examples are proof-of-concept implementations and not production-ready. For a production implementation, consider: + +1. Error handling and retry logic +1. Authentication and security +1. Scalability for multiple concurrent meetings +1. Proper logging and monitoring +1. User management and permissions + +## Resources + +- [Recall.ai Documentation](https://docs.recall.ai/docs/getting-started) +- [Zoom Meeting SDK Documentation](https://marketplace.zoom.us/docs/sdk/native-sdks/web/) +- [Google Meet Bot Examples](https://github.com/Ritika-Das/Google-Meet-Bot) diff --git a/examples/meeting_bot/recall_ai_poc.py b/examples/meeting_bot/recall_ai_poc.py new file mode 100644 index 000000000..818ca78b6 --- /dev/null +++ b/examples/meeting_bot/recall_ai_poc.py @@ -0,0 +1,226 @@ +"""Codegen Meeting Bot - Recall.ai Proof of Concept + +This script demonstrates how to use Recall.ai to create a meeting bot that can: +1. Join a Google Meet or Zoom meeting +2. Record the meeting +3. Generate a transcript +4. Process the transcript with Codegen + +Requirements: +- Recall.ai API key +- Python 3.8+ +- Required packages: requests, asyncio, websockets + +Note: This is a proof-of-concept and not production-ready code. +""" + +import asyncio +import json +import os +import time +from typing import Any, Optional + +import requests + +# Configuration +RECALL_API_KEY = os.environ.get("RECALL_API_KEY", "your_api_key_here") +RECALL_API_BASE_URL = "https://api.recall.ai/api/v1" + + +class CodegenMeetingBot: + """A meeting bot that uses Recall.ai to join meetings and process transcripts with Codegen.""" + + def __init__(self, api_key: str): + """Initialize the meeting bot with the Recall.ai API key.""" + self.api_key = api_key + self.headers = {"Authorization": f"Token {self.api_key}", "Content-Type": "application/json"} + + def create_bot(self, platform: str, meeting_url: str, bot_name: str = "Codegen Assistant", join_at: Optional[str] = None) -> dict[str, Any]: + """Create a bot to join a meeting. + + Args: + platform: The meeting platform ("zoom", "google_meet", "ms_teams", etc.) + meeting_url: The URL of the meeting to join + bot_name: The display name for the bot + join_at: ISO 8601 timestamp for when the bot should join (None for immediate) + + Returns: + The bot data from the Recall.ai API + """ + endpoint = f"{RECALL_API_BASE_URL}/bot/" + + payload = {"meeting_url": meeting_url, "platform": platform, "bot_name": bot_name} + + if join_at: + payload["join_at"] = join_at + + response = requests.post(endpoint, headers=self.headers, json=payload) + response.raise_for_status() + + return response.json() + + def get_bot(self, bot_id: str) -> dict[str, Any]: + """Get information about a bot. + + Args: + bot_id: The ID of the bot + + Returns: + The bot data from the Recall.ai API + """ + endpoint = f"{RECALL_API_BASE_URL}/bot/{bot_id}/" + + response = requests.get(endpoint, headers=self.headers) + response.raise_for_status() + + return response.json() + + def list_bots(self, limit: int = 10) -> list[dict[str, Any]]: + """List all bots. + + Args: + limit: Maximum number of bots to return + + Returns: + List of bot data from the Recall.ai API + """ + endpoint = f"{RECALL_API_BASE_URL}/bot/?limit={limit}" + + response = requests.get(endpoint, headers=self.headers) + response.raise_for_status() + + return response.json().get("results", []) + + async def stream_transcription(self, bot_id: str): + """Stream real-time transcription from a bot. + + Args: + bot_id: The ID of the bot + """ + import websockets + + # Get the bot data to check if it's active + bot = self.get_bot(bot_id) + + if bot.get("status") != "joined": + print(f"Bot is not active in the meeting. Current status: {bot.get('status')}") + return + + # Connect to the transcription websocket + ws_url = f"wss://api.recall.ai/ws/bot/{bot_id}/transcription/" + + async with websockets.connect(ws_url, extra_headers={"Authorization": f"Token {self.api_key}"}) as websocket: + print("Connected to transcription stream. Waiting for transcription...") + + while True: + try: + message = await websocket.recv() + data = json.loads(message) + + if data.get("event") == "transcript_part": + speaker = data.get("speaker", "Unknown") + text = data.get("text", "") + print(f"{speaker}: {text}") + + # Here you would process the transcript with Codegen + # For example, detect if someone is asking a question about Codegen + if "codegen" in text.lower() and "?" in text: + print("Detected question about Codegen!") + # In a real implementation, you would: + # 1. Process the question with Codegen + # 2. Generate a response + # 3. Send the response back to the meeting (via chat or audio) + + except Exception as e: + print(f"Error in transcription stream: {e}") + break + + def process_meeting_recording(self, bot_id: str) -> dict[str, Any]: + """Process a completed meeting recording. + + Args: + bot_id: The ID of the bot + + Returns: + Processed meeting data + """ + # Get the bot data + bot = self.get_bot(bot_id) + + if bot.get("status") not in ["left", "ended"]: + print(f"Meeting is not completed yet. Current status: {bot.get('status')}") + return {} + + # Get the recording URL + recording_url = bot.get("video_url") + + if not recording_url: + print("No recording available for this meeting.") + return {} + + print(f"Recording available at: {recording_url}") + + # In a real implementation, you would: + # 1. Download the recording + # 2. Process the full transcript + # 3. Generate a meeting summary with Codegen + # 4. Extract action items + # 5. Store the results + + return { + "meeting_id": bot.get("id"), + "duration": bot.get("duration"), + "recording_url": recording_url, + "platform": bot.get("platform"), + "summary": "This is where the meeting summary would go.", + "action_items": ["Action item 1", "Action item 2"], + } + + +async def main(): + """Main function to demonstrate the Codegen Meeting Bot.""" + # Initialize the bot + bot = CodegenMeetingBot(RECALL_API_KEY) + + # Example: Create a bot to join a Google Meet meeting + meeting_url = "https://meet.google.com/abc-defg-hij" + + try: + # Create a bot to join the meeting + print(f"Creating bot to join meeting: {meeting_url}") + bot_data = bot.create_bot(platform="google_meet", meeting_url=meeting_url, bot_name="Codegen Assistant") + + bot_id = bot_data.get("id") + print(f"Bot created with ID: {bot_id}") + print(f"Bot status: {bot_data.get('status')}") + + # Wait for the bot to join the meeting + print("Waiting for bot to join the meeting...") + for _ in range(30): # Wait up to 30 seconds + bot_data = bot.get_bot(bot_id) + if bot_data.get("status") == "joined": + print("Bot has joined the meeting!") + break + time.sleep(1) + + # Stream transcription in real-time + print("Starting transcription stream...") + await bot.stream_transcription(bot_id) + + # In a real implementation, you would wait for the meeting to end + # For this example, we'll just wait a few seconds + print("Simulating meeting duration (10 seconds)...") + time.sleep(10) + + # Process the meeting recording + print("Processing meeting recording...") + meeting_data = bot.process_meeting_recording(bot_id) + print(f"Meeting data: {json.dumps(meeting_data, indent=2)}") + + except Exception as e: + print(f"Error: {e}") + + +if __name__ == "__main__": + # Run the main function + asyncio.run(main()) diff --git a/examples/meeting_bot/zoom_sdk_poc.js b/examples/meeting_bot/zoom_sdk_poc.js new file mode 100644 index 000000000..0c213d6ca --- /dev/null +++ b/examples/meeting_bot/zoom_sdk_poc.js @@ -0,0 +1,447 @@ +/** + * Codegen Meeting Bot - Zoom SDK Proof of Concept + * + * This script demonstrates how to use the Zoom Meeting SDK to create a meeting bot that can: + * 1. Join a Zoom meeting + * 2. Listen to meeting events + * 3. Interact with the meeting + * + * Requirements: + * - Node.js + * - Zoom Meeting SDK credentials + * - Puppeteer for headless browser automation + * + * Note: This is a proof-of-concept and not production-ready code. + */ + +const puppeteer = require("puppeteer"); +const express = require("express"); +const bodyParser = require("body-parser"); +const crypto = require("crypto"); +const cors = require("cors"); +const path = require("path"); +const fs = require("fs"); + +// Configuration +const PORT = process.env.PORT || 3000; +const ZOOM_SDK_KEY = process.env.ZOOM_SDK_KEY || "your_sdk_key_here"; +const ZOOM_SDK_SECRET = process.env.ZOOM_SDK_SECRET || "your_sdk_secret_here"; + +// Express app setup +const app = express(); +app.use(cors()); +app.use(bodyParser.json()); +app.use(express.static("public")); + +// Generate a Zoom Meeting SDK JWT token +function generateZoomToken(sdkKey, sdkSecret, meetingNumber, role) { + const iat = Math.round(new Date().getTime() / 1000) - 30; + const exp = iat + 60 * 60 * 2; // Token expires in 2 hours + const oHeader = { alg: "HS256", typ: "JWT" }; + + const oPayload = { + sdkKey: sdkKey, + mn: meetingNumber, + role: role, + iat: iat, + exp: exp, + tokenExp: exp, + }; + + const sHeader = JSON.stringify(oHeader); + const sPayload = JSON.stringify(oPayload); + const signature = crypto + .createHmac("sha256", sdkSecret) + .update(Buffer.from(sHeader + "." + sPayload).toString("base64")) + .digest("base64"); + + return ( + Buffer.from(sHeader).toString("base64") + + "." + + Buffer.from(sPayload).toString("base64") + + "." + + signature + ); +} + +// API endpoint to generate a token +app.post("/api/token", (req, res) => { + const { meetingNumber, role } = req.body; + + if (!meetingNumber) { + return res.status(400).json({ error: "Meeting number is required" }); + } + + const token = generateZoomToken( + ZOOM_SDK_KEY, + ZOOM_SDK_SECRET, + meetingNumber, + role || 0, + ); + res.json({ token }); +}); + +// HTML template for the Zoom client +const zoomClientHtml = ` + + + + Codegen Zoom Bot + + + + + + + + +
+ + + + + + + + + +`; + +// Serve the Zoom client HTML +app.get("/zoom-client", (req, res) => { + res.send(zoomClientHtml); +}); + +// Start the server +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); + +/** + * CodegenZoomBot class for controlling a Zoom meeting bot + */ +class CodegenZoomBot { + constructor() { + this.browser = null; + this.page = null; + this.serverUrl = `http://localhost:${PORT}`; + this.isConnected = false; + this.meetingData = { + meetingNumber: "", + password: "", + userName: "Codegen Assistant", + }; + } + + /** + * Initialize the bot + */ + async initialize() { + // Launch a headless browser + this.browser = await puppeteer.launch({ + headless: true, + args: [ + "--no-sandbox", + "--disable-setuid-sandbox", + "--disable-web-security", + "--allow-running-insecure-content", + "--use-fake-ui-for-media-stream", + "--use-fake-device-for-media-stream", + ], + }); + + // Create a new page + this.page = await this.browser.newPage(); + + // Set up event listeners for messages from the Zoom client + await this.page.exposeFunction("onClientMessage", (message) => { + const { type, data } = message; + + switch (type) { + case "CLIENT_READY": + console.log("Zoom client is ready"); + break; + case "JOIN_SUCCESS": + console.log("Successfully joined the meeting"); + this.isConnected = true; + break; + case "JOIN_ERROR": + console.error("Failed to join the meeting:", data); + break; + case "CONNECTION_CHANGE": + console.log("Connection changed:", data); + break; + case "AUDIO_CHANGE": + console.log("Audio changed:", data); + break; + case "CHAT_RECEIVED": + console.log("Chat received:", data); + this.processChat(data); + break; + default: + console.log("Unknown message type:", type); + } + }); + + // Set up message listener + await this.page.evaluateOnNewDocument(() => { + window.addEventListener("message", (event) => { + window.onClientMessage(event.data); + }); + }); + + // Navigate to the Zoom client page + await this.page.goto(`${this.serverUrl}/zoom-client`); + + console.log("Bot initialized"); + } + + /** + * Join a Zoom meeting + * @param {string} meetingNumber - The Zoom meeting number + * @param {string} password - The Zoom meeting password + */ + async joinMeeting(meetingNumber, password) { + this.meetingData.meetingNumber = meetingNumber; + this.meetingData.password = password; + + // Get a token for the meeting + const response = await fetch(`${this.serverUrl}/api/token`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + meetingNumber, + role: 0, // 0 for attendee, 1 for host + }), + }); + + const { token } = await response.json(); + + // Send a message to the Zoom client to join the meeting + await this.page.evaluate( + (data) => { + window.postMessage( + { + type: "JOIN_MEETING", + data: { + meetingNumber: data.meetingNumber, + token: data.token, + userName: data.userName, + password: data.password, + }, + }, + "*", + ); + }, + { + meetingNumber, + token, + userName: this.meetingData.userName, + password, + }, + ); + + console.log(`Joining meeting: ${meetingNumber}`); + } + + /** + * Leave the current Zoom meeting + */ + async leaveMeeting() { + if (!this.isConnected) { + console.log("Not connected to a meeting"); + return; + } + + // Send a message to the Zoom client to leave the meeting + await this.page.evaluate(() => { + window.postMessage( + { + type: "LEAVE_MEETING", + }, + "*", + ); + }); + + this.isConnected = false; + console.log("Left the meeting"); + } + + /** + * Process a chat message from the meeting + * @param {Object} chatData - The chat message data + */ + async processChat(chatData) { + const { message, sender } = chatData; + + // Check if the message is directed to Codegen + if (message.toLowerCase().includes("codegen") && message.includes("?")) { + console.log(`Processing question from ${sender}: ${message}`); + + // In a real implementation, you would: + // 1. Process the question with Codegen + // 2. Generate a response + // 3. Send the response back to the chat + + // For this example, we'll just send a hardcoded response + await this.sendChatMessage( + `Hello ${sender}, I'm the Codegen Assistant. I'm here to help with your Codegen questions!`, + ); + } + } + + /** + * Send a chat message to the meeting + * @param {string} message - The message to send + */ + async sendChatMessage(message) { + if (!this.isConnected) { + console.log("Not connected to a meeting"); + return; + } + + // In a real implementation, you would use the Zoom SDK to send a chat message + // For this example, we'll just log the message + console.log(`Sending chat message: ${message}`); + } + + /** + * Close the bot + */ + async close() { + if (this.isConnected) { + await this.leaveMeeting(); + } + + if (this.browser) { + await this.browser.close(); + } + + console.log("Bot closed"); + } +} + +/** + * Example usage + */ +async function main() { + const bot = new CodegenZoomBot(); + + try { + // Initialize the bot + await bot.initialize(); + + // Join a meeting + const meetingNumber = "123456789"; + const password = "password"; + await bot.joinMeeting(meetingNumber, password); + + // Keep the bot running for a while + console.log("Bot is running. Press Ctrl+C to exit."); + + // Handle process termination + process.on("SIGINT", async () => { + console.log("Shutting down..."); + await bot.close(); + process.exit(0); + }); + } catch (error) { + console.error("Error:", error); + await bot.close(); + } +} + +// Run the example if this script is executed directly +if (require.main === module) { + main(); +} + +module.exports = CodegenZoomBot;