diff --git a/typescript-sdk/apps/client-whatsapp-example/.gitignore b/typescript-sdk/apps/client-whatsapp-example/.gitignore new file mode 100644 index 000000000..2a5eb5b3f --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/.gitignore @@ -0,0 +1,44 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# demo config file +.config.json \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/.nvmrc b/typescript-sdk/apps/client-whatsapp-example/.nvmrc new file mode 100644 index 000000000..622e3630e --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/.nvmrc @@ -0,0 +1 @@ +18 \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/README.md b/typescript-sdk/apps/client-whatsapp-example/README.md new file mode 100644 index 000000000..67badf260 --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/README.md @@ -0,0 +1,190 @@ +# AG-UI WhatsApp Example + +A Next.js demonstration application showcasing AG-UI's WhatsApp Business API integration. This example shows how to build a web application that can send WhatsApp messages and receive webhooks. + +## Features + +- **WhatsApp Business API Integration**: Send messages using the WhatsApp Business API +- **Web-based Configuration**: Secure configuration interface for WhatsApp credentials +- **Debug Tools**: Built-in debugging tools to test API credentials +- **Webhook Support**: Receive and process incoming WhatsApp messages +- **Modern UI**: Clean, responsive interface built with Next.js and Tailwind CSS + +## Tech Stack + +- **Framework**: Next.js 15 (App Router) +- **Styling**: Tailwind CSS +- **WhatsApp Integration**: AG-UI Community WhatsApp Package +- **Language**: TypeScript +- **Deployment**: Vercel-ready + +## Prerequisites + +- Node.js 18+ +- WhatsApp Business API account +- Meta Developer Console access + +## Quick Start + +1. **Install dependencies**: + ```bash + pnpm install + ``` + +2. **Run the development server**: + ```bash + pnpm dev + ``` + +3. **Open your browser**: + Navigate to [http://localhost:3000](http://localhost:3000) + +## WhatsApp Business API Setup + +### 1. Create WhatsApp Business API App + +1. Go to [Meta Developer Console](https://developers.facebook.com/) +2. Create a new app or use an existing one +3. Add the **WhatsApp Business API** product +4. Configure your phone number + +### 2. Get Your Credentials + +You'll need these values from your Meta Developer Console: + +- **Phone Number ID**: Found in WhatsApp Business API → Phone Numbers +- **Access Token**: Generated from System Users → Generate Token +- **Webhook Secret**: Create a strong secret for webhook verification +- **Verify Token**: Any string for webhook verification challenges + +### 3. Configure the App + +1. Open the app in your browser +2. Click **"Configure"** in the top right +3. Enter your WhatsApp Business API credentials +4. Save the configuration + +### 4. Test the Integration + +1. **Debug Credentials**: Click "Debug Credentials" to test your setup +2. **Send Messages**: Use the form to send test messages +3. **Check Webhooks**: Configure your webhook URL in Meta Developer Console + +## Project Structure + +``` +client-whatsapp-example/ +├── src/ +│ ├── app/ +│ │ ├── api/ +│ │ │ ├── config/ # Configuration management +│ │ │ ├── debug/ # Debug API endpoint +│ │ │ ├── send-message/ # Send WhatsApp messages +│ │ │ └── webhook/ # Receive webhooks +│ │ ├── config/ # Configuration page +│ │ └── page.tsx # Main application page +│ └── lib/ +│ └── config.ts # Configuration utilities +├── public/ # Static assets +└── README.md # This file +``` + +## API Endpoints + +### Configuration Management +- `GET /api/config` - Get current configuration status +- `POST /api/config` - Save new configuration + +### WhatsApp Integration +- `POST /api/send-message` - Send WhatsApp message +- `GET/POST /api/webhook` - Handle WhatsApp webhooks + +### Debug Tools +- `GET /api/debug` - Test WhatsApp API credentials + +## Environment Variables + +Create a `.env.local` file with your WhatsApp credentials: + +```env +# WhatsApp Business API Configuration +WHATSAPP_PHONE_NUMBER_ID=your_phone_number_id_here +WHATSAPP_ACCESS_TOKEN=your_access_token_here +WHATSAPP_WEBHOOK_SECRET=your_webhook_secret_here +WHATSAPP_VERIFY_TOKEN=your_verify_token_here +``` + +## Deployment + +### Vercel Deployment + +1. **Push to GitHub**: + ```bash + git add . + git commit -m "Add WhatsApp example app" + git push origin main + ``` + +2. **Deploy to Vercel**: + - Connect your GitHub repository to Vercel + - Add environment variables in Vercel dashboard + - Deploy automatically + +### Environment Variables in Production + +Set these in your Vercel dashboard: +- `WHATSAPP_PHONE_NUMBER_ID` +- `WHATSAPP_ACCESS_TOKEN` +- `WHATSAPP_WEBHOOK_SECRET` +- `WHATSAPP_VERIFY_TOKEN` + +## Security Notes + +- **Demo Configuration**: This example uses file-based storage for demo purposes +- **Production**: Use secure environment variables or a database +- **Webhook Security**: Always verify webhook signatures in production +- **Access Tokens**: Keep your access tokens secure and rotate regularly + +## Troubleshooting + +### Common Issues + +1. **"Phone Number ID does not exist"** + - Verify your Phone Number ID in Meta Developer Console + - Check that your access token has the right permissions + +2. **"Missing permissions"** + - Ensure your access token has `whatsapp_business_messaging` permission + - Check that you're using the correct Phone Number ID + +3. **"Webhook verification failed"** + - Verify your webhook secret matches in both places + - Check that your webhook URL is accessible + +### Debug Tools + +Use the built-in debug tools to: +- Test your WhatsApp API credentials +- Verify your Phone Number ID +- Check API permissions +- Validate webhook configuration + +## Contributing + +This is part of the AG-UI project. To contribute: + +1. Fork the AG-UI repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## License + +This example is part of the AG-UI project and follows the same license terms. + +## Related Links + +- [AG-UI Documentation](https://docs.ag-ui.com) +- [WhatsApp Business API Documentation](https://developers.facebook.com/docs/whatsapp) +- [Meta Developer Console](https://developers.facebook.com/) +- [Next.js Documentation](https://nextjs.org/docs) \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/WEBHOOK_TESTING.md b/typescript-sdk/apps/client-whatsapp-example/WEBHOOK_TESTING.md new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/WEBHOOK_TESTING.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/package.json b/typescript-sdk/apps/client-whatsapp-example/package.json new file mode 100644 index 000000000..e571d280a --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/package.json @@ -0,0 +1,30 @@ +{ + "name": "@ag-ui/client-whatsapp-example", + "version": "0.1.0", + "private": true, + "engines": { + "node": ">=18.0.0" + }, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "15.2.1", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "autoprefixer": "^10.0.1", + "eslint": "^8", + "eslint-config-next": "15.2.1", + "postcss": "^8", + "tailwindcss": "^3.3.0", + "typescript": "^5" + } +} \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/postcss.config.js b/typescript-sdk/apps/client-whatsapp-example/postcss.config.js new file mode 100644 index 000000000..a653d3f5a --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/postcss.config.js @@ -0,0 +1,9 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} + +module.exports = config \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/src/app/api/config/route.ts b/typescript-sdk/apps/client-whatsapp-example/src/app/api/config/route.ts new file mode 100644 index 000000000..3067a1468 --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/src/app/api/config/route.ts @@ -0,0 +1,68 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getConfig, setConfig } from "@/lib/config"; + +export async function GET() { + try { + console.log("Config GET API called"); + const config = getConfig(); + console.log("Config from getConfig:", config ? "exists" : "null"); + + if (!config) { + console.log("No configuration found, returning 404"); + return NextResponse.json({ error: "No configuration found" }, { status: 404 }); + } + + // Return configuration without sensitive data + const response = { + phoneNumberId: config.phoneNumberId, + hasAccessToken: !!config.accessToken, + hasWebhookSecret: !!config.webhookSecret, + hasVerifyToken: !!config.verifyToken, + }; + console.log("Returning config status:", response); + return NextResponse.json(response); + } catch (error) { + console.error("Config GET error:", error); + return NextResponse.json( + { error: "Failed to retrieve configuration" }, + { status: 500 } + ); + } +} + +export async function POST(request: NextRequest) { + try { + console.log("Config POST API called"); + const config = await request.json(); + console.log("Received config data:", { + phoneNumberId: config.phoneNumberId ? "provided" : "missing", + hasAccessToken: !!config.accessToken, + hasWebhookSecret: !!config.webhookSecret, + hasVerifyToken: !!config.verifyToken, + }); + + // Validate required fields + if (!config.phoneNumberId || !config.accessToken || !config.webhookSecret || !config.verifyToken) { + console.log("Missing required fields"); + return NextResponse.json( + { error: "All fields are required" }, + { status: 400 } + ); + } + + // Store configuration (in production, this would be in a secure database) + setConfig(config); + console.log("Configuration saved successfully"); + + return NextResponse.json({ + success: true, + message: "Configuration saved successfully", + }); + } catch (error) { + console.error("Config POST error:", error); + return NextResponse.json( + { error: "Failed to save configuration" }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/src/app/api/debug/route.ts b/typescript-sdk/apps/client-whatsapp-example/src/app/api/debug/route.ts new file mode 100644 index 000000000..b200ebdc1 --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/src/app/api/debug/route.ts @@ -0,0 +1,64 @@ +import { NextResponse } from "next/server"; +import { getConfig } from "@/lib/config"; + +export async function GET() { + try { + console.log("Debug API called"); + const config = getConfig(); + + if (!config) { + return NextResponse.json({ + error: "No configuration found", + config: null + }); + } + + // Test the WhatsApp API credentials + const testUrl = `https://graph.facebook.com/v23.0/${config.phoneNumberId}`; + console.log("Testing WhatsApp API with URL:", testUrl); + + const response = await fetch(testUrl, { + headers: { + 'Authorization': `Bearer ${config.accessToken}`, + 'Content-Type': 'application/json', + }, + }); + + const responseData = await response.json(); + console.log("WhatsApp API Response:", responseData); + + if (!response.ok) { + return NextResponse.json({ + error: "WhatsApp API test failed", + status: response.status, + statusText: response.statusText, + response: responseData, + config: { + phoneNumberId: config.phoneNumberId, + hasAccessToken: !!config.accessToken, + hasWebhookSecret: !!config.webhookSecret, + hasVerifyToken: !!config.verifyToken, + } + }); + } + + return NextResponse.json({ + success: true, + message: "WhatsApp API credentials are valid", + phoneNumberInfo: responseData, + config: { + phoneNumberId: config.phoneNumberId, + hasAccessToken: !!config.accessToken, + hasWebhookSecret: !!config.webhookSecret, + hasVerifyToken: !!config.verifyToken, + } + }); + + } catch (error) { + console.error("Debug API error:", error); + return NextResponse.json({ + error: "Debug API failed", + details: error instanceof Error ? error.message : 'Unknown error' + }, { status: 500 }); + } +} \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/src/app/api/send-message/route.ts b/typescript-sdk/apps/client-whatsapp-example/src/app/api/send-message/route.ts new file mode 100644 index 000000000..cad19256c --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/src/app/api/send-message/route.ts @@ -0,0 +1,92 @@ +import { NextRequest, NextResponse } from "next/server"; +import { WhatsAppAgent } from "@/lib/whatsapp-agent"; +import { getConfig } from "@/lib/config"; + +export async function POST(request: NextRequest) { + try { + console.log("Send message API called"); + const config = getConfig(); + console.log("Config retrieved:", config ? "exists" : "null"); + + if (!config) { + console.log("No configuration found, returning error"); + return NextResponse.json( + { + error: "WhatsApp configuration not found. Please configure your settings first.", + details: "Missing or invalid configuration" + }, + { status: 500 } + ); + } + + console.log("Creating WhatsApp agent with config"); + console.log("Phone Number ID:", config.phoneNumberId); + console.log("Has Access Token:", !!config.accessToken); + console.log("Has Webhook Secret:", !!config.webhookSecret); + + const agent = new WhatsAppAgent({ + phoneNumberId: config.phoneNumberId, + accessToken: config.accessToken, + webhookSecret: config.webhookSecret, + }); + + const { phoneNumber, message } = await request.json(); + console.log("Sending message to:", phoneNumber); + console.log("Message content:", message); + + if (!phoneNumber || !message) { + return NextResponse.json( + { + error: "phoneNumber and message are required", + details: "Missing required fields" + }, + { status: 400 } + ); + } + + console.log("Calling sendMessageToNumber"); + try { + const response = await agent.sendMessageToNumber(phoneNumber, message); + console.log("Message sent successfully:", response.messages[0].id); + + return NextResponse.json({ + success: true, + messageId: response.messages[0].id, + response, + }); + } catch (sendError) { + console.error("Detailed send error:", { + error: sendError, + message: sendError instanceof Error ? sendError.message : 'Unknown error', + stack: sendError instanceof Error ? sendError.stack : undefined + }); + + // Enhanced error response with detailed information + const errorMessage = sendError instanceof Error ? sendError.message : 'Unknown error'; + + return NextResponse.json( + { + error: "Failed to send WhatsApp message", + details: errorMessage, + status: 500, + response: { + error: errorMessage, + type: "WhatsApp API Error", + timestamp: new Date().toISOString() + } + }, + { status: 500 } + ); + } + } catch (error) { + console.error("Send message error:", error); + return NextResponse.json( + { + error: "Failed to send message", + details: "Unexpected server error", + status: 500 + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/src/app/api/webhook/route.ts b/typescript-sdk/apps/client-whatsapp-example/src/app/api/webhook/route.ts new file mode 100644 index 000000000..9d9e5b29b --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/src/app/api/webhook/route.ts @@ -0,0 +1,67 @@ +import { NextRequest, NextResponse } from "next/server"; +import { WhatsAppAgent } from "@/lib/whatsapp-agent"; +import { getConfig } from "@/lib/config"; + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url); + const mode = searchParams.get("hub.mode"); + const token = searchParams.get("hub.verify_token"); + const challenge = searchParams.get("hub.challenge"); + + console.log("Webhook verification request:", { mode, token, challenge }); + + const config = getConfig(); + if (!config) { + console.log("No configuration found for webhook verification"); + return NextResponse.json({ error: "Configuration not found" }, { status: 500 }); + } + + if (mode === "subscribe" && token === config.verifyToken) { + console.log("Webhook verified successfully"); + return new NextResponse(challenge, { status: 200 }); + } + + console.log("Webhook verification failed"); + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); +} + +export async function POST(request: NextRequest) { + try { + console.log("Webhook POST received"); + const config = getConfig(); + + if (!config) { + console.log("No configuration found for webhook processing"); + return NextResponse.json({ error: "Configuration not found" }, { status: 500 }); + } + + const agent = new WhatsAppAgent({ + phoneNumberId: config.phoneNumberId, + accessToken: config.accessToken, + webhookSecret: config.webhookSecret, + }); + + const body = await request.text(); + console.log("Webhook body:", body); + + // Verify webhook signature + const signature = request.headers.get("x-hub-signature-256"); + if (!signature || !agent.verifyWebhookSignature(body, signature)) { + console.log("Webhook signature verification failed"); + return NextResponse.json({ error: "Invalid signature" }, { status: 401 }); + } + + // Process webhook + const webhookData = JSON.parse(body); + const processedMessages = await agent.processWebhook(webhookData); + + console.log("Webhook processed successfully:", processedMessages); + return NextResponse.json({ + success: true, + processedMessages + }); + } catch (error) { + console.error("Webhook processing error:", error); + return NextResponse.json({ error: "Webhook processing failed" }, { status: 500 }); + } +} \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/src/app/config/page.tsx b/typescript-sdk/apps/client-whatsapp-example/src/app/config/page.tsx new file mode 100644 index 000000000..826b85acf --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/src/app/config/page.tsx @@ -0,0 +1,329 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; + +interface ConfigForm { + phoneNumberId: string; + accessToken: string; + webhookSecret: string; + verifyToken: string; +} + +export default function ConfigPage() { + const router = useRouter(); + const [form, setForm] = useState({ + phoneNumberId: "", + accessToken: "", + webhookSecret: "", + verifyToken: "", + }); + const [isLoading, setIsLoading] = useState(false); + const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null); + const [showSecrets, setShowSecrets] = useState(false); + + useEffect(() => { + // Load existing config from API + fetch("/api/config") + .then(res => res.json()) + .then(data => { + if (!data.error && data.phoneNumberId) { + setForm({ + phoneNumberId: data.phoneNumberId, + accessToken: "", // Don't load sensitive data + webhookSecret: "", // Don't load sensitive data + verifyToken: "", // Don't load sensitive data + }); + } + }) + .catch(() => { + // Config not found, start with empty form + }); + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + setMessage(null); + + try { + const response = await fetch("/api/config", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(form), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "Failed to save configuration"); + } + + setMessage({ + type: "success", + text: "Configuration saved successfully! Redirecting to main page..." + }); + + // Redirect back to main page after a short delay + setTimeout(() => { + router.push("/"); + }, 2000); + } catch (error) { + setMessage({ + type: "error", + text: error instanceof Error ? error.message : "Failed to save configuration. Please try again." + }); + } finally { + setIsLoading(false); + } + }; + + const handleInputChange = (field: keyof ConfigForm, value: string) => { + setForm(prev => ({ + ...prev, + [field]: value + })); + }; + + const generateRandomToken = () => { + const token = Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15); + handleInputChange("verifyToken", token); + }; + + const generateRandomSecret = () => { + const secret = Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15) + + Math.random().toString(36).substring(2, 15); + handleInputChange("webhookSecret", secret); + }; + + return ( +
+
+ {/* Header */} +
+

+ WhatsApp Configuration +

+

+ Configure your WhatsApp Business API settings securely +

+
+ +
+ {/* Security Notice */} +
+
+
+ + + +
+
+

+ Security Notice +

+
+

+ This is a demo application. In production, these secrets should be stored securely + on your server using environment variables or a secure configuration service. +

+
+
+
+
+ + {/* Configuration Form */} +
+
+ {/* Phone Number ID */} +
+ + handleInputChange("phoneNumberId", e.target.value)} + placeholder="123456789012345" + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 placeholder-gray-500" + required + /> +

+ Found in your WhatsApp Business API dashboard +

+
+ + {/* Access Token */} +
+ +
+ handleInputChange("accessToken", e.target.value)} + placeholder="EAA..." + className="w-full px-3 py-2 pr-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 placeholder-gray-500" + required + /> + +
+

+ Generated from your Meta Developer account +

+
+ + {/* Webhook Secret */} +
+ +
+
+ handleInputChange("webhookSecret", e.target.value)} + placeholder="your-webhook-secret" + className="w-full px-3 py-2 pr-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 placeholder-gray-500" + required + /> + +
+ +
+

+ Used to verify webhook signatures +

+
+ + {/* Verify Token */} +
+ +
+ handleInputChange("verifyToken", e.target.value)} + placeholder="your-verify-token" + className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-gray-900 placeholder-gray-500" + required + /> + +
+

+ Used for webhook verification challenge +

+
+ + {/* Message */} + {message && ( +
+ {message.text} +
+ )} + + {/* Action Buttons */} +
+ + +
+
+
+ + {/* Help Section */} +
+

How to Get These Values

+
+
+

Phone Number ID

+

Found in your WhatsApp Business API dashboard under Phone Numbers

+
+
+

Access Token

+

Generate from Meta Developer Console → System Users → Generate Token

+
+
+

Webhook Secret

+

Create a strong secret for verifying webhook signatures

+
+
+

Verify Token

+

Any string you choose for webhook verification challenges

+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/typescript-sdk/apps/client-whatsapp-example/src/app/globals.css b/typescript-sdk/apps/client-whatsapp-example/src/app/globals.css new file mode 100644 index 000000000..f89e035ea --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/src/app/globals.css @@ -0,0 +1,39 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; + overflow-x: hidden; +} + +/* Ensure proper text wrapping and prevent overflow */ +pre { + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.error-container { + max-width: 100%; + overflow-x: auto; +} + +.error-text { + word-break: break-word; + overflow-wrap: break-word; +} diff --git a/typescript-sdk/apps/client-whatsapp-example/src/app/layout.tsx b/typescript-sdk/apps/client-whatsapp-example/src/app/layout.tsx new file mode 100644 index 000000000..516db8782 --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/src/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "AG-UI WhatsApp Demo", + description: "A lean demonstration of AG-UI WhatsApp integration", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/typescript-sdk/apps/client-whatsapp-example/src/app/page.tsx b/typescript-sdk/apps/client-whatsapp-example/src/app/page.tsx new file mode 100644 index 000000000..cddf23d5f --- /dev/null +++ b/typescript-sdk/apps/client-whatsapp-example/src/app/page.tsx @@ -0,0 +1,414 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; + +interface SendMessageResult { + success: boolean; + messageId: string; + response: { + messaging_product: string; + contacts: Array<{ + input: string; + wa_id: string; + }>; + messages: Array<{ + id: string; + }>; + }; +} + +interface ConfigStatus { + phoneNumberId: string; + hasAccessToken: boolean; + hasWebhookSecret: boolean; + hasVerifyToken: boolean; +} + +interface DebugResult { + success?: boolean; + error?: string; + message?: string; + phoneNumberInfo?: Record; + status?: number; + response?: Record; +} + +interface ErrorDetails { + message: string; + details?: string; + status?: number; + response?: Record; +} + +export default function Home() { + const [phoneNumber, setPhoneNumber] = useState(""); + const [message, setMessage] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + const [configStatus, setConfigStatus] = useState(null); + const [debugResult, setDebugResult] = useState(null); + const [isDebugLoading, setIsDebugLoading] = useState(false); + + useEffect(() => { + // Check configuration status + fetch("/api/config") + .then(res => res.json()) + .then(data => { + if (data.error) { + setConfigStatus(null); + } else { + setConfigStatus(data); + } + }) + .catch(() => { + setConfigStatus(null); + }); + }, []); + + const handleSendMessage = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + setError(null); + setResult(null); + + try { + const response = await fetch("/api/send-message", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ phoneNumber, message }), + }); + + const data = await response.json(); + + if (!response.ok) { + // Enhanced error handling with detailed information + const errorDetails: ErrorDetails = { + message: data.error || "Failed to send message", + status: response.status, + details: data.details || undefined, + response: data.response || undefined + }; + setError(errorDetails); + return; + } + + setResult(data); + } catch (err) { + setError({ + message: err instanceof Error ? err.message : "An error occurred", + details: "Network or unexpected error" + }); + } finally { + setIsLoading(false); + } + }; + + const handleDebugCredentials = async () => { + setIsDebugLoading(true); + setDebugResult(null); + + try { + const response = await fetch("/api/debug"); + const data = await response.json(); + setDebugResult(data); + } catch (err) { + setDebugResult({ + error: err instanceof Error ? err.message : "Debug failed" + }); + } finally { + setIsDebugLoading(false); + } + }; + + const isConfigured = configStatus && + configStatus.phoneNumberId && + configStatus.hasAccessToken && + configStatus.hasWebhookSecret && + configStatus.hasVerifyToken; + + return ( +
+
+ {/* Header */} +
+

+ AG-UI WhatsApp Demo +

+

+ A lean demonstration of AG-UI WhatsApp integration. Send messages and + receive webhooks through the WhatsApp Business API. +

+
+ + {/* Main Content */} +
+ {/* Configuration Status */} +
+
+

+ Configuration Status +

+
+ + + Configure + +
+
+ + {configStatus ? ( +
+
+
+ + Phone Number ID: {configStatus.phoneNumberId ? "Configured" : "Not configured"} + +
+
+
+ + Access Token: {configStatus.hasAccessToken ? "Configured" : "Not configured"} + +
+
+
+ + Webhook Secret: {configStatus.hasWebhookSecret ? "Configured" : "Not configured"} + +
+
+
+ + Verify Token: {configStatus.hasVerifyToken ? "Configured" : "Not configured"} + +
+
+ ) : ( +
+

No configuration found

+ + Set Up Configuration + +
+ )} +
+ + {/* Debug Results */} + {debugResult && ( +
+

+ Debug Results +

+ {debugResult.success ? ( +
+

✅ {debugResult.message || 'Credentials are valid'}

+ {debugResult.phoneNumberInfo && ( +
+ Phone Number Details +
+                        {JSON.stringify(debugResult.phoneNumberInfo, null, 2)}
+                      
+
+ )} +
+ ) : ( +
+

❌ {debugResult.error}

+ {debugResult.status && ( +

Status: {debugResult.status}

+ )} + {debugResult.response && ( +
+ Error Details +
+                        {JSON.stringify(debugResult.response, null, 2)}
+                      
+
+ )} +
+ )} +
+ )} + + {/* Send Message Form */} +
+

+ Send WhatsApp Message +

+ + {!isConfigured ? ( +
+
+ + + +
+

+ Please configure your WhatsApp Business API settings first. +

+ + Configure Now + +
+ ) : ( +
+
+ + setPhoneNumber(e.target.value)} + placeholder="+1234567890" + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent text-gray-900 placeholder-gray-500" + required + /> +
+
+ +