Skip to content

Commit 3f9cfd7

Browse files
committed
add examples/nextjs-simple
1 parent 9015513 commit 3f9cfd7

14 files changed

Lines changed: 2235 additions & 2 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Runway API secret (required)
2+
# Get yours at https://dev.runwayml.com/
3+
RUNWAYML_API_SECRET=

examples/nextjs-simple/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.next
3+
.env.local

examples/nextjs-simple/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Next.js Avatar Example
2+
3+
This example shows how to use `@runwayml/avatars-react` with [Next.js](https://nextjs.org/) App Router.
4+
5+
## How to use
6+
7+
```bash
8+
npx degit runwayml/avatars-sdk-react/examples/nextjs my-avatar-app
9+
cd my-avatar-app
10+
npm install
11+
```
12+
13+
## Configuration
14+
15+
Copy the example environment file and add your Runway API key:
16+
17+
```bash
18+
cp .env.example .env.local
19+
```
20+
21+
Then edit `.env.local` with your key from [dev.runwayml.com](https://dev.runwayml.com/).
22+
23+
## Running locally
24+
25+
```bash
26+
npm install
27+
npm run dev
28+
```
29+
30+
Open [http://localhost:3000](http://localhost:3000) and navigate to the avatar page.
31+
32+
## Custom Avatars
33+
34+
In addition to the preset avatars, you can use your own custom avatars created in the [Runway Developer Portal](https://dev.runwayml.com/).
35+
36+
1. Create a custom avatar in the Developer Portal
37+
2. Copy the avatar ID
38+
3. Enter it in the "Custom Avatar" input on the example page
39+
4. Click "Start Call"
40+
41+
The API route automatically handles both preset and custom avatar types.
42+
43+
## Learn More
44+
45+
- [Runway Avatar SDK Documentation](https://github.com/runwayml/avatars-sdk-react)
46+
- [Next.js Documentation](https://nextjs.org/docs)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Runway from '@runwayml/sdk';
2+
import { RunwayRealtime } from '../../../../runway-realtime';
3+
4+
const client = new Runway({ apiKey: process.env.RUNWAYML_API_SECRET });
5+
const realtime = new RunwayRealtime(client);
6+
7+
export async function POST(req: Request) {
8+
const { avatarId } = await req.json();
9+
10+
const avatar = { type: 'runway-preset' as const, presetId: avatarId }
11+
// If you want to use a custom avatar, you can use the following code:
12+
// const avatar = { type: 'custom' as const, avatarId: avatarId }
13+
14+
const { id: sessionId } = await realtime.create({
15+
model: 'gwm1_avatars',
16+
avatar,
17+
});
18+
19+
const { sessionKey } = await realtime.waitForReady(sessionId);
20+
21+
return Response.json({ sessionId, sessionKey });
22+
}
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
* {
2+
box-sizing: border-box;
3+
margin: 0;
4+
padding: 0;
5+
}
6+
7+
:root {
8+
--foreground: #171717;
9+
--background: #ffffff;
10+
--muted: #737373;
11+
--border: #e5e5e5;
12+
--radius: 12px;
13+
}
14+
15+
@media (prefers-color-scheme: dark) {
16+
:root {
17+
--foreground: #ededed;
18+
--background: #0a0a0a;
19+
--muted: #a3a3a3;
20+
--border: #262626;
21+
}
22+
}
23+
24+
html,
25+
body {
26+
height: 100%;
27+
}
28+
29+
body {
30+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
31+
background: var(--background);
32+
color: var(--foreground);
33+
line-height: 1.5;
34+
-webkit-font-smoothing: antialiased;
35+
}
36+
37+
/* Layout */
38+
.page {
39+
min-height: 100vh;
40+
min-height: 100dvh;
41+
display: flex;
42+
flex-direction: column;
43+
align-items: center;
44+
justify-content: center;
45+
padding: 24px 24px 64px;
46+
gap: 48px;
47+
}
48+
49+
@media (max-width: 600px) {
50+
.page {
51+
padding: 16px;
52+
gap: 32px;
53+
justify-content: flex-start;
54+
padding-top: 48px;
55+
}
56+
}
57+
58+
/* Header */
59+
.header {
60+
text-align: center;
61+
max-width: 600px;
62+
}
63+
64+
.logo {
65+
height: 24px;
66+
margin-bottom: 16px;
67+
}
68+
69+
.title {
70+
font-size: clamp(32px, 6vw, 48px);
71+
font-weight: 600;
72+
letter-spacing: -0.03em;
73+
line-height: 1.1;
74+
margin-bottom: 16px;
75+
}
76+
77+
.description {
78+
font-size: 16px;
79+
color: var(--muted);
80+
max-width: 480px;
81+
margin: 0 auto;
82+
}
83+
84+
@media (max-width: 600px) {
85+
.description {
86+
font-size: 15px;
87+
}
88+
}
89+
90+
/* Preset Grid */
91+
.presets {
92+
display: flex;
93+
justify-content: center;
94+
gap: 24px;
95+
width: 100%;
96+
}
97+
98+
.preset {
99+
position: relative;
100+
display: flex;
101+
flex-direction: column;
102+
justify-content: flex-end;
103+
width: 240px;
104+
aspect-ratio: 3 / 4;
105+
background: var(--background);
106+
border: 1px solid var(--border);
107+
border-radius: var(--radius);
108+
cursor: pointer;
109+
overflow: hidden;
110+
transition: border-color 0.15s ease;
111+
}
112+
113+
.preset:focus-visible {
114+
outline: 2px solid var(--foreground);
115+
outline-offset: 2px;
116+
}
117+
118+
.preset-avatar {
119+
position: absolute;
120+
inset: 0;
121+
width: 100%;
122+
height: 100%;
123+
object-fit: cover;
124+
transition: transform 0.3s ease;
125+
}
126+
127+
.preset-info {
128+
position: relative;
129+
display: flex;
130+
flex-direction: column;
131+
gap: 4px;
132+
padding: 16px;
133+
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
134+
}
135+
136+
@media (max-width: 480px) {
137+
.preset-info {
138+
padding: 12px;
139+
gap: 2px;
140+
}
141+
}
142+
143+
.preset-name {
144+
font-size: 15px;
145+
font-weight: 500;
146+
color: #ffffff;
147+
}
148+
149+
.preset-subtitle {
150+
font-size: 13px;
151+
color: rgba(255, 255, 255, 0.7);
152+
}
153+
154+
@media (max-width: 480px) {
155+
.preset-name {
156+
font-size: 14px;
157+
}
158+
159+
.preset-subtitle {
160+
font-size: 12px;
161+
}
162+
}
163+
164+
/* Modal */
165+
.modal-overlay {
166+
position: fixed;
167+
inset: 0;
168+
background: rgba(0, 0, 0, 0.9);
169+
display: flex;
170+
align-items: center;
171+
justify-content: center;
172+
padding: 24px;
173+
z-index: 100;
174+
animation: fadeIn 0.2s ease;
175+
}
176+
177+
@media (max-width: 600px) {
178+
.modal-overlay {
179+
padding: 16px;
180+
}
181+
}
182+
183+
@keyframes fadeIn {
184+
from { opacity: 0; }
185+
to { opacity: 1; }
186+
}
187+
188+
.modal {
189+
width: 100%;
190+
max-width: 800px;
191+
animation: slideUp 0.25s ease;
192+
}
193+
194+
195+
@keyframes slideUp {
196+
from {
197+
opacity: 0;
198+
transform: translateY(20px);
199+
}
200+
to {
201+
opacity: 1;
202+
transform: translateY(0);
203+
}
204+
}
205+
206+
.modal-header {
207+
display: flex;
208+
justify-content: space-between;
209+
align-items: center;
210+
margin-bottom: 16px;
211+
}
212+
213+
214+
.modal-title {
215+
font-size: 16px;
216+
font-weight: 500;
217+
color: #ffffff;
218+
}
219+
220+
.modal-close {
221+
background: transparent;
222+
border: none;
223+
color: #a3a3a3;
224+
cursor: pointer;
225+
padding: 8px;
226+
display: flex;
227+
align-items: center;
228+
justify-content: center;
229+
border-radius: 8px;
230+
transition: color 0.15s ease, background 0.15s ease;
231+
}
232+
233+
.modal-close:hover {
234+
color: #ffffff;
235+
background: rgba(255, 255, 255, 0.1);
236+
}
237+
238+
.modal-loading {
239+
display: flex;
240+
align-items: center;
241+
justify-content: center;
242+
min-height: 400px;
243+
color: #a3a3a3;
244+
font-size: 1rem;
245+
}
246+
247+
@media (prefers-reduced-motion: reduce) {
248+
.modal-overlay,
249+
.modal,
250+
.preset-avatar {
251+
animation: none;
252+
transition: none;
253+
}
254+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Metadata } from 'next';
2+
import './globals.css';
3+
4+
export const metadata: Metadata = {
5+
title: 'Runway Avatar SDK',
6+
description: 'Build real-time avatar experiences with React',
7+
};
8+
9+
export default function RootLayout({
10+
children,
11+
}: {
12+
children: React.ReactNode;
13+
}) {
14+
return (
15+
<html lang="en">
16+
<body>{children}</body>
17+
</html>
18+
);
19+
}

0 commit comments

Comments
 (0)