Install
Copy
Ask AI
npm install @odysseyml/odyssey
npm package version
"@odysseyml/odyssey": "^0.3.0" requiredHTML
For standalone HTML files without a bundler, import directly from a CDN:Copy
Ask AI
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: system-ui; max-width: 600px; margin: 2rem auto; }
video { width: 100%; background: #000; }
input { flex: 1; padding: 0.5rem; }
button { padding: 0.5rem 1rem; }
.row { display: flex; gap: 0.5rem; margin-top: 0.5rem; }
</style>
</head>
<body>
<video id="video" autoplay playsinline muted></video>
<p id="status">Loading...</p>
<div class="row">
<input id="prompt" placeholder="Enter stream prompt..." />
<button id="send">Send</button>
<button id="end" disabled>End</button>
</div>
<script type="module">
import { Odyssey } from 'https://esm.sh/@odysseyml/odyssey';
const client = new Odyssey({ apiKey: 'ody_your_api_key_here' });
const status = document.getElementById('status');
const prompt = document.getElementById('prompt');
const endBtn = document.getElementById('end');
let isStreaming = false;
// Cleanup on page unload
window.addEventListener('beforeunload', () => client.disconnect());
status.textContent = 'Connecting...';
const mediaStream = await client.connect();
document.getElementById('video').srcObject = mediaStream;
status.textContent = 'Connected';
document.getElementById('send').onclick = async () => {
const text = prompt.value.trim();
if (!text) return;
prompt.value = '';
if (isStreaming) {
await client.interact(text);
} else {
status.textContent = 'Starting...';
await client.startStream(text);
isStreaming = true;
endBtn.disabled = false;
status.textContent = 'Streaming';
}
};
endBtn.onclick = async () => {
await client.endStream();
isStreaming = false;
endBtn.disabled = true;
status.textContent = 'Connected';
};
</script>
</body>
</html>
Always ensure
disconnect() is called when done (via page unload handlers or component cleanup). Stale connections count towards your concurrent session limit (max 1), which will block new connections until they time out. If disconnect() is not called, connections are automatically cleared after 40 seconds on the server side.JavaScript
Copy
Ask AI
import { Odyssey } from '@odysseyml/odyssey';
const client = new Odyssey({ apiKey: 'ody_your_api_key_here' });
const mediaStream = await client.connect();
document.querySelector('video').srcObject = mediaStream;
await client.startStream('A cat');
await client.interact('Make it jump');
await client.endStream();
client.disconnect();
React
Copy
Ask AI
import { Odyssey } from '@odysseyml/odyssey';
import { useRef, useEffect, useState } from 'react';
const client = new Odyssey({ apiKey: 'ody_your_api_key_here' });
function App() {
const videoRef = useRef<HTMLVideoElement>(null);
const [prompt, setPrompt] = useState('');
const [status, setStatus] = useState('Disconnected');
const [isConnected, setIsConnected] = useState(false);
const [isStreaming, setIsStreaming] = useState(false);
// Cleanup on component unmount
useEffect(() => {
return () => client.disconnect();
}, []);
const handleConnect = async () => {
setStatus('Connecting...');
const mediaStream = await client.connect();
if (videoRef.current) videoRef.current.srcObject = mediaStream;
setIsConnected(true);
setStatus('Connected');
};
const handleSend = async () => {
if (!prompt.trim()) return;
const text = prompt;
setPrompt('');
if (isStreaming) {
await client.interact(text);
} else {
setStatus('Starting...');
await client.startStream(text);
setIsStreaming(true);
setStatus('Streaming');
}
};
const handleEnd = async () => {
await client.endStream();
setIsStreaming(false);
setStatus('Connected');
};
return (
<div>
<video ref={videoRef} autoPlay playsInline muted />
<p>Status: {status}</p>
<button onClick={handleConnect} disabled={isConnected}>Connect</button>
<input value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="Enter stream prompt..." disabled={!isConnected} />
<button onClick={handleSend} disabled={!isConnected}>Send</button>
<button onClick={handleEnd} disabled={!isStreaming}>End</button>
</div>
);
}
Next.js
Copy
Ask AI
'use client';
import { Odyssey } from '@odysseyml/odyssey';
import { useRef, useEffect, useState } from 'react';
const client = new Odyssey({ apiKey: 'ody_your_api_key_here' });
export default function OdysseyDemo() {
const videoRef = useRef<HTMLVideoElement>(null);
const [prompt, setPrompt] = useState('');
const [status, setStatus] = useState('Disconnected');
const [isConnected, setIsConnected] = useState(false);
const [isStreaming, setIsStreaming] = useState(false);
// Cleanup on component unmount
useEffect(() => {
return () => client.disconnect();
}, []);
const handleConnect = async () => {
setStatus('Connecting...');
const mediaStream = await client.connect();
if (videoRef.current) videoRef.current.srcObject = mediaStream;
setIsConnected(true);
setStatus('Connected');
};
const handleSend = async () => {
if (!prompt.trim()) return;
const text = prompt;
setPrompt('');
if (isStreaming) {
await client.interact(text);
} else {
setStatus('Starting...');
await client.startStream(text);
setIsStreaming(true);
setStatus('Streaming');
}
};
const handleEnd = async () => {
await client.endStream();
setIsStreaming(false);
setStatus('Connected');
};
return (
<div>
<video ref={videoRef} autoPlay playsInline muted style={{ width: '100%', background: '#000' }} />
<p>Status: {status}</p>
<button onClick={handleConnect} disabled={isConnected} style={{ padding: '0.5rem 1rem', marginRight: '0.5rem' }}>
Connect
</button>
<input
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Enter stream prompt..."
disabled={!isConnected}
style={{ padding: '0.5rem', marginRight: '0.5rem' }}
/>
<button onClick={handleSend} disabled={!isConnected} style={{ padding: '0.5rem 1rem', marginRight: '0.5rem' }}>Send</button>
<button onClick={handleEnd} disabled={!isStreaming} style={{ padding: '0.5rem 1rem' }}>End</button>
</div>
);
}
Next.js App Router requires the
'use client' directive for components using React hooks and browser APIs like video elements.Python
Copy
Ask AI
import asyncio
from odyssey import Odyssey, OdysseyAuthError, OdysseyConnectionError
async def main():
client = Odyssey(api_key="ody_your_api_key_here")
try:
await client.connect(
on_video_frame=lambda frame: print(f"Frame: {frame.width}x{frame.height}"),
on_stream_started=lambda stream_id: print(f"Ready: {stream_id}"),
)
await client.start_stream("A cat", portrait=True)
await client.interact("Pet the cat")
await client.end_stream()
except OdysseyAuthError:
print("Invalid API key")
except OdysseyConnectionError as e:
print(f"Connection failed: {e}")
finally:
await client.disconnect()
asyncio.run(main())
Next Steps
Discord Community
Get help and share what you’re building.