Antigravity × Vercel AI SDK Integration
Combining Vercel AI SDK with Antigravity enables rapid development of high-performance AI chat applications. This guide covers practical integration techniques in detail.
Vercel AI SDK Overview
Vercel AI SDK is a JavaScript/TypeScript library for building AI-powered user interfaces. It supports multiple LLM providers and offers streaming, tool calling, conversation management, and more.
Key Features
- Streaming responses: Real-time text generation display
- Multi-provider support: OpenAI, Anthropic, and more
- Tool calling: LLM invokes external APIs
- Conversation management: Automatic history handling
- Multimodal support: Text, images, file inputs
Setup and Initial Configuration
Project Initialization
# Create Next.js project
npx create-next-app@latest ai-chat-app \
--typescript \
--tailwind \
--eslint
cd ai-chat-app
# Install Vercel AI SDK and dependencies
npm install ai
# Install Antigravity SDK
npm install @antigravitylab/sdk
# Setup environment variables
cat > .env.local << 'EOF'
ANTIGRAVITY_API_KEY=your-api-key-here
NEXT_PUBLIC_API_URL=http://localhost:3000
EOFpackage.json Configuration
{
"name": "antigravity-ai-chat",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"ai": "^3.0.0",
"@antigravitylab/sdk": "^1.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"next": "^14.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"@types/react": "^18.0.0",
"typescript": "^5.0.0",
"tailwindcss": "^3.0.0",
"autoprefixer": "^10.0.0"
}
}useChat Hook Implementation
Building Basic Chat UI
// app/page.tsx
'use client';
import { useChat } from 'ai/react';
import { useState } from 'react';
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat',
initialMessages: [
{
id: 'system',
role: 'assistant',
content: 'Hello! I\'m Antigravity AI Assistant. How can I help you today?',
},
],
});
return (
<div className="flex flex-col h-screen bg-gray-900 text-white">
{/* Message display area */}
<div className="flex-1 overflow-y-auto p-6 space-y-4">
{messages.map((message) => (
<div
key={message.id}
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-xs lg:max-w-md xl:max-w-lg px-4 py-2 rounded-lg ${
message.role === 'user'
? 'bg-blue-600 text-white'
: 'bg-gray-700 text-gray-100'
}`}
>
{message.content}
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-gray-700 px-4 py-2 rounded-lg">
<div className="flex space-x-2">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.4s' }}></div>
</div>
</div>
</div>
)}
</div>
{/* Input form */}
<form onSubmit={handleSubmit} className="border-t border-gray-700 p-4">
<div className="flex gap-4">
<input
type="text"
value={input}
onChange={handleInputChange}
placeholder="Type your message..."
disabled={isLoading}
className="flex-1 px-4 py-2 bg-gray-800 border border-gray-600 rounded-lg focus:outline-none focus:border-blue-500 disabled:opacity-50"
/>
<button
type="submit"
disabled={isLoading}
className="px-6 py-2 bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 transition-colors"
>
Send
</button>
</div>
</form>
</div>
);
}API Handler Implementation
// app/api/chat/route.ts
import { Message, StreamingTextResponse } from 'ai';
import Antigravity from '@antigravitylab/sdk';
const client = new Antigravity({
apiKey: process.env.ANTIGRAVITY_API_KEY,
});
export async function POST(req: Request) {
const { messages } = await req.json();
// Generate streaming response with Antigravity agent
const stream = await client.streamCompletion({
messages: messages.map((msg: Message) => ({
role: msg.role,
content: msg.content,
})),
model: 'claude-3-opus',
system: `
You are a professional Antigravity IDE assistant.
Provide accurate and practical answers to user questions.
Include code examples when appropriate.
`,
temperature: 0.7,
maxTokens: 2048,
});
return new StreamingTextResponse(stream);
}useCompletion Hook Integration
Text Generation UI
// app/components/TextGeneration.tsx
'use client';
import { useCompletion } from 'ai/react';
import { useState } from 'react';
export function TextGenerationPage() {
const { completion, input, handleInputChange, handleSubmit, isLoading } = useCompletion({
api: '/api/completion',
onFinish: (prompt, completion) => {
console.log('Generation complete:', completion);
},
});
const [generatedText, setGeneratedText] = useState('');
const handleGenerateCustom = async (prompt: string) => {
const result = await fetch('/api/completion', {
method: 'POST',
body: JSON.stringify({ prompt }),
});
const { text } = await result.json();
setGeneratedText(text);
};
return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-3xl font-bold mb-6">Text Generation Tool</h1>
{/* Preset templates */}
<div className="grid grid-cols-2 gap-4 mb-8">
<button
onClick={() => handleGenerateCustom('Generate 5 blog post title ideas')}
className="p-4 bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors"
>
Blog Title Generator
</button>
<button
onClick={() => handleGenerateCustom('Write a product description')}
className="p-4 bg-purple-600 rounded-lg hover:bg-purple-700 transition-colors"
>
Product Description
</button>
</div>
{/* Input form */}
<form onSubmit={handleSubmit} className="mb-8">
<textarea
value={input}
onChange={handleInputChange}
placeholder="Enter custom prompt..."
className="w-full px-4 py-2 bg-gray-800 border border-gray-600 rounded-lg mb-4"
rows={4}
/>
<button
type="submit"
disabled={isLoading}
className="px-6 py-2 bg-green-600 rounded-lg hover:bg-green-700 disabled:opacity-50"
>
Generate
</button>
</form>
{/* Generated result */}
{completion && (
<div className="bg-gray-800 p-6 rounded-lg border border-gray-700">
<h2 className="text-xl font-bold mb-4">Generated Result</h2>
<p className="text-gray-200 whitespace-pre-wrap">{completion}</p>
</div>
)}
</div>
);
}Completion API Handler
// app/api/completion/route.ts
import { StreamingTextResponse } from 'ai';
import Antigravity from '@antigravitylab/sdk';
const client = new Antigravity({
apiKey: process.env.ANTIGRAVITY_API_KEY,
});
export async function POST(req: Request) {
const { prompt } = await req.json();
const stream = await client.streamCompletion({
messages: [
{
role: 'user',
content: prompt,
},
],
model: 'claude-3-opus',
maxTokens: 1024,
});
return new StreamingTextResponse(stream);
}Tool Calling (Function Calling)
Tool Definition and Handlers
// lib/tools.ts
export const tools = {
getCurrentWeather: {
description: 'Get current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City name',
},
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Temperature unit',
},
},
required: ['location'],
},
},
getCodeReview: {
description: 'Review code and provide improvement suggestions',
parameters: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'Code to review',
},
language: {
type: 'string',
description: 'Programming language',
},
},
required: ['code', 'language'],
},
},
executeCommand: {
description: 'Execute shell command',
parameters: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Command to execute',
},
cwd: {
type: 'string',
description: 'Working directory',
},
},
required: ['command'],
},
},
};
// Tool execution function
export async function executeTool(
toolName: string,
toolInput: Record<string, unknown>
): Promise<string> {
switch (toolName) {
case 'getCurrentWeather':
return await getWeather(toolInput.location as string);
case 'getCodeReview':
return await reviewCode(
toolInput.code as string,
toolInput.language as string
);
case 'executeCommand':
return await executeShellCommand(
toolInput.command as string,
toolInput.cwd as string
);
default:
return `Unknown tool: ${toolName}`;
}
}
async function getWeather(location: string): Promise<string> {
// Implementation: call weather API
return `Current temperature in ${location}: 20°C`;
}
async function reviewCode(code: string, language: string): Promise<string> {
// Implementation: code review logic
return `${language} code review: improvements needed`;
}
async function executeShellCommand(
command: string,
cwd?: string
): Promise<string> {
// Implementation: shell command execution
return `Command execution result: ${command}`;
}Tool Calling Integration
// app/api/chat-with-tools/route.ts
import { Message, StreamingTextResponse } from 'ai';
import Antigravity from '@antigravitylab/sdk';
import { tools, executeTool } from '@/lib/tools';
const client = new Antigravity({
apiKey: process.env.ANTIGRAVITY_API_KEY,
});
export async function POST(req: Request) {
const { messages } = await req.json();
// Stream with tool calling support
const response = await client.streamChatWithTools({
messages,
tools,
onToolCall: async (toolName, toolInput) => {
const result = await executeTool(toolName, toolInput);
return {
toolName,
result,
};
},
model: 'claude-3-opus',
});
return new StreamingTextResponse(response);
}Multimodal Input
Image and File Input Processing
// app/components/MultimodalChat.tsx
'use client';
import { useState, useRef } from 'react';
import { useChat } from 'ai/react';
export function MultimodalChat() {
const fileInputRef = useRef<HTMLInputElement>(null);
const [attachedFiles, setAttachedFiles] = useState<File[]>([]);
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat-multimodal',
});
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
setAttachedFiles(prev => [...prev, ...files]);
};
const handleRemoveFile = (index: number) => {
setAttachedFiles(prev => prev.filter((_, i) => i !== index));
};
const handleSubmitWithFiles = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Encode files to Base64
const filesData = await Promise.all(
attachedFiles.map(async file => ({
name: file.name,
type: file.type,
data: await fileToBase64(file),
}))
);
// Include files in form data
const formData = new FormData(e.currentTarget);
formData.append('files', JSON.stringify(filesData));
// Submit logic here
setAttachedFiles([]);
};
const fileToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsDataURL(file);
});
};
return (
<div className="flex flex-col h-screen bg-gray-900 text-white">
<div className="flex-1 overflow-y-auto p-6">
{messages.map(msg => (
<div key={msg.id} className="mb-4">
<span className="font-bold">{msg.role}:</span> {msg.content}
</div>
))}
</div>
<form onSubmit={handleSubmitWithFiles} className="border-t border-gray-700 p-4">
{/* Attached files list */}
{attachedFiles.length > 0 && (
<div className="mb-4 space-y-2">
{attachedFiles.map((file, index) => (
<div key={index} className="flex items-center justify-between bg-gray-800 p-2 rounded">
<span className="text-sm">{file.name}</span>
<button
type="button"
onClick={() => handleRemoveFile(index)}
className="text-red-400 hover:text-red-300"
>
Remove
</button>
</div>
))}
</div>
)}
{/* Input field */}
<div className="flex gap-4">
<button
type="button"
onClick={() => fileInputRef.current?.click()}
className="px-4 py-2 bg-gray-700 rounded hover:bg-gray-600"
>
Add Files
</button>
<input
ref={fileInputRef}
type="file"
multiple
onChange={handleFileSelect}
accept="image/*,.pdf,.txt,.md"
className="hidden"
/>
<input
type="text"
value={input}
onChange={handleInputChange}
placeholder="Type message..."
className="flex-1 px-4 py-2 bg-gray-800 border border-gray-600 rounded"
/>
<button
type="submit"
disabled={isLoading}
className="px-6 py-2 bg-blue-600 rounded hover:bg-blue-700 disabled:opacity-50"
>
Send
</button>
</div>
</form>
</div>
);
}Multimodal API Handler
// app/api/chat-multimodal/route.ts
import { StreamingTextResponse } from 'ai';
import Antigravity from '@antigravitylab/sdk';
const client = new Antigravity({
apiKey: process.env.ANTIGRAVITY_API_KEY,
});
export async function POST(req: Request) {
const formData = await req.formData();
const message = formData.get('message') as string;
const filesJson = formData.get('files') as string;
const files = JSON.parse(filesJson || '[]');
const stream = await client.streamCompletion({
messages: [
{
role: 'user',
content: message,
attachments: files.map(f => ({
type: 'document',
mimeType: f.type,
data: f.data,
})),
},
],
model: 'claude-3-opus',
});
return new StreamingTextResponse(stream);
}Advanced Features
Chat History Persistence
// lib/db.ts
import { Message } from 'ai';
export class ChatHistoryDB {
static async saveMessage(conversationId: string, message: Message) {
// Save to database
const response = await fetch('/api/history', {
method: 'POST',
body: JSON.stringify({ conversationId, message }),
});
return response.json();
}
static async loadHistory(conversationId: string): Promise<Message[]> {
const response = await fetch(`/api/history?conversationId=${conversationId}`);
return response.json();
}
static async deleteHistory(conversationId: string) {
await fetch(`/api/history/${conversationId}`, { method: 'DELETE' });
}
}Custom Hook
// hooks/useCustomChat.ts
import { useChat } from 'ai/react';
import { useEffect, useState } from 'react';
import { ChatHistoryDB } from '@/lib/db';
export function useCustomChat(conversationId: string) {
const [isLoading, setIsLoading] = useState(true);
const chat = useChat({
api: '/api/chat',
});
useEffect(() => {
// Load conversation history
ChatHistoryDB.loadHistory(conversationId)
.then(messages => {
// Restore messages
setIsLoading(false);
});
}, [conversationId]);
// Save messages on submission
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
await chat.handleSubmit(e);
// Save latest message
if (chat.messages.length > 0) {
const lastMessage = chat.messages[chat.messages.length - 1];
await ChatHistoryDB.saveMessage(conversationId, lastMessage);
}
};
return {
...chat,
isLoading: isLoading || chat.isLoading,
handleSubmit,
};
}Deployment and Best Practices
Deploy to Vercel
# Initialize repository
git init
git add .
git commit -m "Initial commit"
# Deploy to Vercel
vercel deploy
# Configure environment variables
vercel env add ANTIGRAVITY_API_KEYSecurity and Performance
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// API key validation
const apiKey = request.headers.get('Authorization');
if (!apiKey) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// Rate limiting
const ip = request.ip || 'unknown';
const rateLimit = checkRateLimit(ip);
if (!rateLimit) {
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{ status: 429 }
);
}
return NextResponse.next();
}
function checkRateLimit(ip: string): boolean {
// Implementation: rate limit checking
return true;
}
export const config = {
matcher: '/api/:path*',
};Practical Example: Custom AI Assistant
// app/components/AntgravityAssistant.tsx
'use client';
import { useChat } from 'ai/react';
export function AntgravityAssistant() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/assistant',
system: `
You are Antigravity AI Assistant.
Help users with:
- Antigravity IDE usage
- Code analysis and suggestions
- Project setup guidance
- Performance optimization tips
`,
});
return (
<div className="max-w-2xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Antigravity AI Assistant</h1>
<div className="space-y-4 mb-6 h-96 overflow-y-auto bg-gray-800 p-4 rounded-lg">
{messages.map(msg => (
<div key={msg.id} className={msg.role === 'user' ? 'text-right' : 'text-left'}>
<div className={`inline-block px-4 py-2 rounded-lg max-w-xs ${
msg.role === 'user' ? 'bg-blue-600' : 'bg-gray-700'
}`}>
{msg.content}
</div>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
type="text"
value={input}
onChange={handleInputChange}
placeholder="Ask a question..."
className="flex-1 px-4 py-2 bg-gray-800 border border-gray-600 rounded"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading}
className="px-6 py-2 bg-blue-600 rounded hover:bg-blue-700 disabled:opacity-50"
>
{isLoading ? 'Sending...' : 'Send'}
</button>
</form>
</div>
);
}Looking back
Combining Antigravity with Vercel AI SDK enables:
- Real-time streaming conversation UIs
- Multi-provider LLM support
- Advanced tool calling capabilities
- Multimodal input handling
- Scalable deployment
Explore more advanced AI applications with these powerful tools.