Installation
- React / Next.js
- React Native
npm install @gr33n-ai/jade-sdk-client
# or
yarn add @gr33n-ai/jade-sdk-client
# or
pnpm add @gr33n-ai/jade-sdk-client
npm install @gr33n-ai/jade-sdk-rn-client @react-native-async-storage/async-storage react-native-safe-area-context
# or
yarn add @gr33n-ai/jade-sdk-rn-client @react-native-async-storage/async-storage react-native-safe-area-context
The SDK uses
react-native-sse internally for Server-Sent Events streaming,
which is bundled with the package.cd ios && pod install
Setup
1. Get Your API Key
Create an API key in your organization dashboard.2. Add the Provider
- React / Next.js
- React Native
Wrap your application with
JadeProvider:// app.tsx
import { JadeProvider } from '@gr33n-ai/jade-sdk-client';
export default function App() {
return (
<JadeProvider
config={{
endpoint: 'https://api.gr33n.ai',
getAuthToken: () => process.env.NEXT_PUBLIC_JADE_API_KEY,
}}
>
<YourApp />
</JadeProvider>
);
}
Never expose your API key in client-side code for production applications.
See the Authentication Guide for secure patterns.
Wrap your application with
JadeProvider and configure storage:// App.tsx
import { JadeProvider, createAsyncStorageAdapter } from '@gr33n-ai/jade-sdk-rn-client';
import AsyncStorage from '@react-native-async-storage/async-storage';
const storage = createAsyncStorageAdapter(AsyncStorage);
export default function App() {
return (
<JadeProvider
config={{
endpoint: 'https://api.gr33n.ai',
getAuthToken: async () => await AsyncStorage.getItem('@gr33n-ai/api-key'),
}}
storage={storage}
>
<Navigation />
</JadeProvider>
);
}
3. Use the Session Hook
- React / Next.js
- React Native
The
useJadeSession hook provides everything you need for a chat interface:import { useJadeSession } from '@gr33n-ai/jade-sdk-client';
function Chat() {
const {
processedConversation, // Processed entries ready for display
media, // All generated media
isStreaming, // Whether AI is responding
sendMessage, // Send a message
cancel, // Cancel current stream
} = useJadeSession();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = e.currentTarget;
const input = form.elements.namedItem('message') as HTMLInputElement;
if (input.value.trim()) {
sendMessage(input.value);
input.value = '';
}
};
return (
<div>
{/* Conversation */}
<div>
{processedConversation.map((entry, i) => (
<div key={i}>
<strong>{entry.title || entry.displayType}</strong>
{entry.mediaUrls?.map(url => (
<img key={url} src={url} alt="Generated" />
))}
</div>
))}
</div>
{/* Input */}
<form onSubmit={handleSubmit}>
<input
name="message"
placeholder="Generate an image of..."
disabled={isStreaming}
/>
<button type="submit" disabled={isStreaming}>
Send
</button>
{isStreaming && (
<button type="button" onClick={cancel}>
Cancel
</button>
)}
</form>
{/* Media Gallery */}
{media.length > 0 && (
<div>
<h3>Generated Media</h3>
{media.map(m => (
<div key={m.url}>
{m.type === 'image' && <img src={m.url} alt={m.prompt} />}
{m.type === 'video' && <video src={m.url} controls />}
{m.type === 'audio' && <audio src={m.url} controls />}
</div>
))}
</div>
)}
</div>
);
}
import { useJadeSession } from '@gr33n-ai/jade-sdk-rn-client';
import { View, Text, TextInput, FlatList, Image, Button } from 'react-native';
import { useState } from 'react';
function ChatScreen() {
const [input, setInput] = useState('');
const {
processedConversation,
media,
isStreaming,
sendMessage,
} = useJadeSession();
const handleSend = () => {
if (input.trim() && !isStreaming) {
sendMessage(input);
setInput('');
}
};
return (
<View style={{ flex: 1, padding: 16 }}>
{/* Messages */}
<FlatList
data={processedConversation}
keyExtractor={(_, i) => i.toString()}
renderItem={({ item }) => (
<View style={{
padding: 12,
marginVertical: 4,
backgroundColor: item.originalType === 'user_text' ? '#e3f2fd' : '#f5f5f5',
borderRadius: 8,
}}>
<Text style={{ fontWeight: 'bold' }}>
{item.originalType === 'user_text' ? 'You' : 'Jade'}
</Text>
<Text>{item.entry.text}</Text>
{item.mediaUrls?.map(url => (
<Image
key={url}
source={{ uri: url }}
style={{ width: '100%', height: 200, marginTop: 8, borderRadius: 8 }}
resizeMode="cover"
/>
))}
</View>
)}
/>
{/* Input */}
<View style={{ flexDirection: 'row', gap: 8 }}>
<TextInput
value={input}
onChangeText={setInput}
placeholder="Describe what you want to generate..."
style={{
flex: 1,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
padding: 12,
}}
/>
<Button
title={isStreaming ? '...' : 'Send'}
onPress={handleSend}
disabled={isStreaming}
/>
</View>
</View>
);
}
Complete Example
- React / Next.js
- React Native
'use client';
import { useState } from 'react';
import { JadeProvider, useJadeSession } from '@gr33n-ai/jade-sdk-client';
function ChatInterface() {
const [input, setInput] = useState('');
const { processedConversation, media, isStreaming, sendMessage } = useJadeSession();
const handleSend = () => {
if (input.trim() && !isStreaming) {
sendMessage(input);
setInput('');
}
};
return (
<div style={{ maxWidth: 600, margin: '0 auto', padding: 20 }}>
<h1>Jade AI</h1>
{/* Messages */}
<div style={{ minHeight: 300, marginBottom: 20 }}>
{processedConversation.map((entry, i) => (
<div
key={i}
style={{
padding: 10,
margin: '10px 0',
background: entry.originalType === 'user_text' ? '#e3f2fd' : '#f5f5f5',
borderRadius: 8,
}}
>
<strong>{entry.originalType === 'user_text' ? 'You' : 'Jade'}</strong>
<p>{entry.entry.text}</p>
{entry.mediaUrls?.map(url => (
<img key={url} src={url} style={{ maxWidth: '100%' }} />
))}
</div>
))}
{isStreaming && <div>Generating...</div>}
</div>
{/* Generated Media */}
{media.length > 0 && (
<div style={{ marginBottom: 20 }}>
<h3>Generated Images</h3>
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
{media.filter(m => m.type === 'image').map(m => (
<img key={m.url} src={m.url} style={{ width: 150, height: 150, objectFit: 'cover' }} />
))}
</div>
</div>
)}
{/* Input */}
<div style={{ display: 'flex', gap: 10 }}>
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleSend()}
placeholder="Describe what you want to generate..."
style={{ flex: 1, padding: 10 }}
/>
<button onClick={handleSend} disabled={isStreaming}>
{isStreaming ? 'Generating...' : 'Send'}
</button>
</div>
</div>
);
}
export default function App() {
return (
<JadeProvider
config={{
endpoint: 'https://api.gr33n.ai',
getAuthToken: () => 'your-api-key',
}}
>
<ChatInterface />
</JadeProvider>
);
}
import { useState } from 'react';
import {
View,
Text,
TextInput,
FlatList,
Image,
TouchableOpacity,
StyleSheet,
ActivityIndicator,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import {
JadeProvider,
useJadeSession,
createAsyncStorageAdapter,
} from '@gr33n-ai/jade-sdk-rn-client';
import AsyncStorage from '@react-native-async-storage/async-storage';
const storage = createAsyncStorageAdapter(AsyncStorage);
function ChatScreen() {
const [input, setInput] = useState('');
const { processedConversation, media, isStreaming, sendMessage, cancel } = useJadeSession();
const handleSend = () => {
if (input.trim() && !isStreaming) {
sendMessage(input);
setInput('');
}
};
return (
<SafeAreaView style={styles.container}>
{/* Media Gallery */}
{media.length > 0 && (
<View style={styles.gallery}>
<FlatList
horizontal
data={media.filter(m => m.type === 'image')}
keyExtractor={item => item.url}
renderItem={({ item }) => (
<Image source={{ uri: item.url }} style={styles.thumbnail} />
)}
showsHorizontalScrollIndicator={false}
/>
</View>
)}
{/* Messages */}
<FlatList
style={styles.messageList}
data={processedConversation}
keyExtractor={(_, i) => i.toString()}
renderItem={({ item }) => (
<View
style={[
styles.message,
item.originalType === 'user_text' ? styles.userMessage : styles.aiMessage,
]}
>
<Text style={styles.messageLabel}>
{item.originalType === 'user_text' ? 'You' : 'Jade'}
</Text>
{item.entry.text && <Text>{item.entry.text}</Text>}
{item.mediaUrls?.map(url => (
<Image key={url} source={{ uri: url }} style={styles.messageImage} />
))}
</View>
)}
inverted={false}
/>
{/* Streaming Indicator */}
{isStreaming && (
<View style={styles.streamingIndicator}>
<ActivityIndicator size="small" />
<Text style={styles.streamingText}>Generating...</Text>
<TouchableOpacity onPress={cancel}>
<Text style={styles.cancelButton}>Cancel</Text>
</TouchableOpacity>
</View>
)}
{/* Input */}
<View style={styles.inputContainer}>
<TextInput
value={input}
onChangeText={setInput}
placeholder="Generate an image of..."
style={styles.input}
multiline
/>
<TouchableOpacity
style={[styles.sendButton, isStreaming && styles.sendButtonDisabled]}
onPress={handleSend}
disabled={isStreaming}
>
<Text style={styles.sendButtonText}>Send</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
export default function App() {
return (
<JadeProvider
config={{
endpoint: 'https://api.gr33n.ai',
getAuthToken: async () => await AsyncStorage.getItem('@gr33n-ai/api-key'),
}}
storage={storage}
>
<ChatScreen />
</JadeProvider>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
gallery: { height: 80, borderBottomWidth: 1, borderBottomColor: '#eee' },
thumbnail: { width: 60, height: 60, margin: 10, borderRadius: 8 },
messageList: { flex: 1, padding: 16 },
message: { padding: 12, marginVertical: 4, borderRadius: 12 },
userMessage: { backgroundColor: '#e3f2fd', alignSelf: 'flex-end', maxWidth: '80%' },
aiMessage: { backgroundColor: '#f5f5f5', alignSelf: 'flex-start', maxWidth: '80%' },
messageLabel: { fontWeight: 'bold', marginBottom: 4 },
messageImage: { width: '100%', height: 200, borderRadius: 8, marginTop: 8 },
streamingIndicator: { flexDirection: 'row', alignItems: 'center', padding: 8, gap: 8 },
streamingText: { color: '#666' },
cancelButton: { color: '#f44336' },
inputContainer: { flexDirection: 'row', padding: 16, gap: 8, borderTopWidth: 1, borderTopColor: '#eee' },
input: { flex: 1, borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12, maxHeight: 100 },
sendButton: { backgroundColor: '#10B981', paddingHorizontal: 20, borderRadius: 8, justifyContent: 'center' },
sendButtonDisabled: { backgroundColor: '#ccc' },
sendButtonText: { color: '#fff', fontWeight: 'bold' },
});
Example Apps
For complete, runnable examples, check out the jade-sdk-examples repository:- React Native - Full Expo app with chat UI, media gallery, custom tool UIs, and session management
What’s Next?
Authentication
Learn secure patterns for API key management.
Streaming
Understand SSE events and streaming patterns.
Media Handling
Display and manage generated media.
Session Management
Persist and restore conversation sessions.