openclaw-liveavatar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-build-manifest.json +42 -0
  3. package/.next/app-path-routes-manifest.json +6 -0
  4. package/.next/build-manifest.json +33 -0
  5. package/.next/cache/.previewinfo +1 -0
  6. package/.next/cache/.rscinfo +1 -0
  7. package/.next/cache/.tsbuildinfo +1 -0
  8. package/.next/cache/chrome-devtools-workspace-uuid +1 -0
  9. package/.next/cache/next-devtools-config.json +1 -0
  10. package/.next/cache/webpack/client-production/0.pack +0 -0
  11. package/.next/cache/webpack/client-production/1.pack +0 -0
  12. package/.next/cache/webpack/client-production/2.pack +0 -0
  13. package/.next/cache/webpack/client-production/3.pack +0 -0
  14. package/.next/cache/webpack/client-production/4.pack +0 -0
  15. package/.next/cache/webpack/client-production/index.pack +0 -0
  16. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  17. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  18. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  19. package/.next/cache/webpack/server-production/0.pack +0 -0
  20. package/.next/cache/webpack/server-production/index.pack +0 -0
  21. package/.next/diagnostics/build-diagnostics.json +6 -0
  22. package/.next/diagnostics/framework.json +1 -0
  23. package/.next/export-marker.json +6 -0
  24. package/.next/images-manifest.json +58 -0
  25. package/.next/next-minimal-server.js.nft.json +1 -0
  26. package/.next/next-server.js.nft.json +1 -0
  27. package/.next/package.json +1 -0
  28. package/.next/prerender-manifest.json +61 -0
  29. package/.next/react-loadable-manifest.json +1 -0
  30. package/.next/required-server-files.json +320 -0
  31. package/.next/routes-manifest.json +53 -0
  32. package/.next/server/app/_not-found/page.js +5 -0
  33. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  34. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  35. package/.next/server/app/_not-found.html +4 -0
  36. package/.next/server/app/_not-found.meta +8 -0
  37. package/.next/server/app/_not-found.rsc +15 -0
  38. package/.next/server/app/api/get-avatars/route.js +1 -0
  39. package/.next/server/app/api/get-avatars/route.js.nft.json +1 -0
  40. package/.next/server/app/api/get-avatars/route_client-reference-manifest.js +1 -0
  41. package/.next/server/app/api/start-session/route.js +1 -0
  42. package/.next/server/app/api/start-session/route.js.nft.json +1 -0
  43. package/.next/server/app/api/start-session/route_client-reference-manifest.js +1 -0
  44. package/.next/server/app/index.html +4 -0
  45. package/.next/server/app/index.meta +7 -0
  46. package/.next/server/app/index.rsc +16 -0
  47. package/.next/server/app/page.js +9 -0
  48. package/.next/server/app/page.js.nft.json +1 -0
  49. package/.next/server/app/page_client-reference-manifest.js +1 -0
  50. package/.next/server/app-paths-manifest.json +6 -0
  51. package/.next/server/chunks/361.js +9 -0
  52. package/.next/server/chunks/611.js +6 -0
  53. package/.next/server/chunks/873.js +22 -0
  54. package/.next/server/functions-config-manifest.json +4 -0
  55. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  56. package/.next/server/middleware-build-manifest.js +1 -0
  57. package/.next/server/middleware-manifest.json +6 -0
  58. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  59. package/.next/server/next-font-manifest.js +1 -0
  60. package/.next/server/next-font-manifest.json +1 -0
  61. package/.next/server/pages/404.html +4 -0
  62. package/.next/server/pages/500.html +1 -0
  63. package/.next/server/pages/_app.js +1 -0
  64. package/.next/server/pages/_app.js.nft.json +1 -0
  65. package/.next/server/pages/_document.js +1 -0
  66. package/.next/server/pages/_document.js.nft.json +1 -0
  67. package/.next/server/pages/_error.js +19 -0
  68. package/.next/server/pages/_error.js.nft.json +1 -0
  69. package/.next/server/pages-manifest.json +6 -0
  70. package/.next/server/server-reference-manifest.js +1 -0
  71. package/.next/server/server-reference-manifest.json +1 -0
  72. package/.next/server/webpack-runtime.js +1 -0
  73. package/.next/static/chunks/144d3bae-37bcc55d23f188ee.js +1 -0
  74. package/.next/static/chunks/255-35bf8c00c5dde345.js +1 -0
  75. package/.next/static/chunks/336-a66237a0a1db954a.js +1 -0
  76. package/.next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  77. package/.next/static/chunks/app/_not-found/page-dfc6e5d8e6c6203c.js +1 -0
  78. package/.next/static/chunks/app/api/get-avatars/route-8017e1cff542d5d0.js +1 -0
  79. package/.next/static/chunks/app/api/start-session/route-8017e1cff542d5d0.js +1 -0
  80. package/.next/static/chunks/app/layout-ff675313cc8f8fcf.js +1 -0
  81. package/.next/static/chunks/app/page-9e4b703722bef650.js +1 -0
  82. package/.next/static/chunks/framework-de98b93a850cfc71.js +1 -0
  83. package/.next/static/chunks/main-1a0dcce460eb61ce.js +1 -0
  84. package/.next/static/chunks/main-app-e7f1007edc7ad7e1.js +1 -0
  85. package/.next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  86. package/.next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  87. package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  88. package/.next/static/chunks/webpack-4a462cecab786e93.js +1 -0
  89. package/.next/static/css/bfd73afa11897439.css +3 -0
  90. package/.next/static/v_GdCj8lVweDVhmIhhEcM/_buildManifest.js +1 -0
  91. package/.next/static/v_GdCj8lVweDVhmIhhEcM/_ssgManifest.js +1 -0
  92. package/.next/trace +2 -0
  93. package/.next/types/app/api/get-avatars/route.ts +347 -0
  94. package/.next/types/app/api/start-session/route.ts +347 -0
  95. package/.next/types/app/layout.ts +84 -0
  96. package/.next/types/app/page.ts +84 -0
  97. package/.next/types/cache-life.d.ts +141 -0
  98. package/.next/types/package.json +1 -0
  99. package/.next/types/routes.d.ts +74 -0
  100. package/.next/types/validator.ts +88 -0
  101. package/README.md +241 -0
  102. package/app/api/config.ts +18 -0
  103. package/app/api/get-avatars/route.ts +117 -0
  104. package/app/api/start-session/route.ts +95 -0
  105. package/app/globals.css +3 -0
  106. package/app/layout.tsx +37 -0
  107. package/app/page.tsx +9 -0
  108. package/bin/cli.js +100 -0
  109. package/package.json +66 -0
  110. package/src/components/LiveAvatarSession.tsx +825 -0
  111. package/src/components/OpenClawDemo.tsx +399 -0
  112. package/src/gateway/client.ts +522 -0
  113. package/src/gateway/types.ts +83 -0
  114. package/src/liveavatar/context.tsx +750 -0
  115. package/src/liveavatar/index.ts +6 -0
  116. package/src/liveavatar/types.ts +10 -0
  117. package/src/liveavatar/useAvatarActions.ts +41 -0
  118. package/src/liveavatar/useChatHistory.ts +7 -0
  119. package/src/liveavatar/useSession.ts +37 -0
  120. package/src/liveavatar/useTextChat.ts +32 -0
  121. package/src/liveavatar/useVoiceChat.ts +70 -0
  122. package/tsconfig.json +40 -0
package/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # OpenClaw LiveAvatar
2
+
3
+ Give your OpenClaw agent a face and voice! Talk face-to-face with a real-time AI avatar powered by [LiveAvatar](https://liveavatar.com).
4
+
5
+ ![OpenClaw LiveAvatar Demo](public/demo.png)
6
+
7
+ ## Features
8
+
9
+ - **Voice-to-Voice Conversation**: Speak naturally and hear your agent respond
10
+ - **Real-time Avatar**: Lip-synced video avatar with natural expressions
11
+ - **OpenClaw Integration**: Connects to your local OpenClaw Gateway
12
+ - **Smart TTS Summarization**: Long responses are summarized for natural speech
13
+ - **Echo Cancellation**: Won't respond to itself
14
+ - **Multiple Avatar Choices**: Select from custom or public avatars
15
+ - **Chat Transcript**: View the full conversation history
16
+
17
+ ## Installation
18
+
19
+ ### Option 1: ClawHub Skill (Recommended)
20
+
21
+ If you have [OpenClaw](https://openclaw.ai) installed:
22
+
23
+ ```bash
24
+ clawhub install liveavatar
25
+ ```
26
+
27
+ Then run the `/liveavatar` command in any OpenClaw chat.
28
+
29
+ ### Option 2: NPX (Quick Start)
30
+
31
+ ```bash
32
+ # Set your API key
33
+ export LIVEAVATAR_API_KEY=your_key_here
34
+
35
+ # Start OpenClaw Gateway (in another terminal)
36
+ openclaw gateway
37
+
38
+ # Run LiveAvatar
39
+ npx openclaw-liveavatar
40
+ ```
41
+
42
+ ### Option 3: Global Install
43
+
44
+ ```bash
45
+ npm install -g openclaw-liveavatar
46
+
47
+ # Then run anytime with:
48
+ openclaw-liveavatar
49
+ ```
50
+
51
+ ### Option 4: Development Setup
52
+
53
+ ```bash
54
+ git clone https://github.com/eNNNo/openclaw-liveavatar.git
55
+ cd openclaw-liveavatar
56
+ npm install
57
+ cp .env.example .env.local
58
+ # Edit .env.local with your API key
59
+ npm run dev
60
+ ```
61
+
62
+ ## Prerequisites
63
+
64
+ - Node.js 18+
65
+ - [OpenClaw](https://openclaw.ai) installed with Gateway running
66
+ - [LiveAvatar API Key](https://app.liveavatar.com) (free tier available)
67
+
68
+ ## Setup
69
+
70
+ ### 1. Get Your API Key (Free)
71
+
72
+ 1. Go to [app.liveavatar.com](https://app.liveavatar.com)
73
+ 2. Create a free account
74
+ 3. Copy your API key from the dashboard
75
+
76
+ ### 2. Set Your API Key
77
+
78
+ **Option A: Environment variable**
79
+ ```bash
80
+ export LIVEAVATAR_API_KEY=your_api_key_here
81
+ ```
82
+
83
+ **Option B: OpenClaw config** (`~/.openclaw/openclaw.json`)
84
+ ```json
85
+ {
86
+ "skills": {
87
+ "entries": {
88
+ "liveavatar": {
89
+ "env": {
90
+ "LIVEAVATAR_API_KEY": "your_api_key_here"
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ ### 3. Start OpenClaw Gateway
99
+
100
+ ```bash
101
+ openclaw gateway
102
+ ```
103
+
104
+ ### 4. Launch LiveAvatar
105
+
106
+ ```bash
107
+ npx openclaw-liveavatar
108
+ # Or: /liveavatar (if installed as skill)
109
+ ```
110
+
111
+ The interface will open at http://localhost:3001
112
+
113
+ > **Demo Mode**: If OpenClaw Gateway isn't running, the app will start in Demo Mode where you can interact with the avatar and learn about the integration.
114
+
115
+ ## How It Works
116
+
117
+ ```
118
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
119
+ │ You Speak │────▶│ LiveAvatar │────▶│ OpenClaw │
120
+ │ (Microphone) │ │ (Transcribe) │ │ Gateway │
121
+ └─────────────────┘ └──────────────────┘ └────────┬────────┘
122
+
123
+ ┌─────────────────┐ ┌──────────────────┐ │
124
+ │ Avatar Speaks │◀────│ LiveAvatar │◀─────────────┘
125
+ │ (Lip-sync) │ │ (TTS + Video) │ Agent Response
126
+ └─────────────────┘ └──────────────────┘
127
+ ```
128
+
129
+ 1. **You speak** into your microphone
130
+ 2. **LiveAvatar transcribes** your speech to text
131
+ 3. **OpenClaw Gateway** receives the text and sends it to your agent
132
+ 4. **Your agent responds** with text
133
+ 5. **LiveAvatar synthesizes** the response as speech
134
+ 6. **The avatar speaks** with synchronized lip movements
135
+
136
+ ## Configuration
137
+
138
+ ### Environment Variables
139
+
140
+ | Variable | Description | Default |
141
+ |----------|-------------|---------|
142
+ | `LIVEAVATAR_API_KEY` | Your LiveAvatar API key (required) | - |
143
+ | `OPENCLAW_GATEWAY_URL` | WebSocket URL for OpenClaw Gateway | `ws://127.0.0.1:18789` |
144
+ | `OPENCLAW_GATEWAY_TOKEN` | Token for remote Gateway access | - |
145
+
146
+ ### OpenClaw Skill Installation
147
+
148
+ This can also be installed as an OpenClaw skill:
149
+
150
+ ```bash
151
+ clawhub install liveavatar
152
+ ```
153
+
154
+ Or invoke directly:
155
+
156
+ ```
157
+ /liveavatar
158
+ ```
159
+
160
+ ## Development
161
+
162
+ ```bash
163
+ # Install dependencies
164
+ npm install
165
+
166
+ # Start development server
167
+ npm run dev
168
+
169
+ # Build for production
170
+ npm run build
171
+
172
+ # Start production server
173
+ npm start
174
+ ```
175
+
176
+ ## Architecture
177
+
178
+ ```
179
+ openclaw-liveavatar/
180
+ ├── app/ # Next.js app directory
181
+ │ ├── api/ # API routes
182
+ │ │ ├── config.ts # Configuration
183
+ │ │ ├── start-session/ # Session token generation
184
+ │ │ └── get-avatars/ # Avatar listing
185
+ │ ├── layout.tsx # Root layout
186
+ │ └── page.tsx # Main page
187
+ ├── src/
188
+ │ ├── components/ # React components
189
+ │ │ ├── OpenClawDemo.tsx # Setup/landing UI
190
+ │ │ └── LiveAvatarSession.tsx # Session UI
191
+ │ ├── gateway/ # OpenClaw Gateway client
192
+ │ │ ├── client.ts # WebSocket client
193
+ │ │ └── types.ts # Protocol types
194
+ │ └── liveavatar/ # LiveAvatar SDK hooks
195
+ │ ├── context.tsx # React context with Gateway bridge
196
+ │ ├── useSession.ts # Session management
197
+ │ ├── useVoiceChat.ts # Voice chat controls
198
+ │ └── ...
199
+ ├── skills/ # OpenClaw skill definition
200
+ │ └── liveavatar/
201
+ │ └── SKILL.md
202
+ └── openclaw.plugin.json # Channel plugin manifest
203
+ ```
204
+
205
+ ## Troubleshooting
206
+
207
+ ### "OpenClaw Disconnected"
208
+
209
+ Make sure your OpenClaw Gateway is running:
210
+
211
+ ```bash
212
+ openclaw gateway
213
+ ```
214
+
215
+ ### "No avatars available"
216
+
217
+ Verify your `LIVEAVATAR_API_KEY` is set correctly in `.env.local`
218
+
219
+ ### Avatar not responding to speech
220
+
221
+ 1. Check microphone permissions in your browser
222
+ 2. Ensure the mic is not muted (green button = active)
223
+ 3. Verify Gateway shows "Connected" status
224
+ 4. Try a different microphone from the dropdown
225
+
226
+ ### Connection quality issues
227
+
228
+ LiveAvatar uses WebRTC. For best results:
229
+ - Use a stable internet connection
230
+ - Close other video/audio applications
231
+ - Use Chrome or Edge for best WebRTC support
232
+
233
+ ## Credits
234
+
235
+ - [HeyGen LiveAvatar](https://liveavatar.com) - Real-time AI avatar technology
236
+ - [OpenClaw](https://openclaw.ai) - AI agent gateway
237
+ - Based on [liveavatar-ai-sdr](https://github.com/eNNNo/liveavatar-ai-sdr)
238
+
239
+ ## License
240
+
241
+ MIT
@@ -0,0 +1,18 @@
1
+ // LiveAvatar Configuration
2
+ // Get your free API key from https://app.liveavatar.com/developers
3
+ export const LIVEAVATAR_API_KEY = process.env.LIVEAVATAR_API_KEY || "";
4
+ export const LIVEAVATAR_API_URL = "https://api.liveavatar.com";
5
+
6
+ // Default avatar and voice (can be overridden by user selection)
7
+ export const DEFAULT_AVATAR_ID = "1c690fe7-23e0-49f9-bfba-14344450285b"; // Stock avatar
8
+ export const DEFAULT_VOICE_ID = ""; // Will use avatar's default voice
9
+
10
+ // Sandbox mode for development (uses minimal credits)
11
+ // Note: Not all avatars support sandbox mode - set to false for full avatar access
12
+ export const IS_SANDBOX = false;
13
+
14
+ export const LANGUAGE = "en";
15
+
16
+ // OpenClaw Gateway Configuration
17
+ export const OPENCLAW_GATEWAY_URL = process.env.OPENCLAW_GATEWAY_URL || "ws://127.0.0.1:18789";
18
+ export const OPENCLAW_GATEWAY_TOKEN = process.env.OPENCLAW_GATEWAY_TOKEN || "";
@@ -0,0 +1,117 @@
1
+ import { LIVEAVATAR_API_KEY, LIVEAVATAR_API_URL } from "../config";
2
+
3
+ interface Avatar {
4
+ id: string;
5
+ name: string;
6
+ preview_url?: string;
7
+ status?: string;
8
+ is_custom?: boolean;
9
+ is_expired?: boolean;
10
+ default_voice?: {
11
+ id: string;
12
+ name: string;
13
+ };
14
+ }
15
+
16
+ interface AvatarResponse {
17
+ data?: {
18
+ results?: Avatar[];
19
+ total?: number;
20
+ page?: number;
21
+ limit?: number;
22
+ };
23
+ }
24
+
25
+ // Fetch all pages from a paginated endpoint
26
+ async function fetchAllAvatars(endpoint: string): Promise<Avatar[]> {
27
+ const allAvatars: Avatar[] = [];
28
+ let page = 1;
29
+ const limit = 100;
30
+
31
+ while (true) {
32
+ const res = await fetch(`${LIVEAVATAR_API_URL}${endpoint}?page=${page}&limit=${limit}`, {
33
+ headers: {
34
+ "X-API-KEY": LIVEAVATAR_API_KEY!,
35
+ },
36
+ });
37
+
38
+ if (!res.ok) {
39
+ console.error(`Failed to fetch ${endpoint} page ${page}:`, res.status);
40
+ break;
41
+ }
42
+
43
+ const data: AvatarResponse = await res.json();
44
+ const pageAvatars = data.data?.results || [];
45
+ allAvatars.push(...pageAvatars);
46
+
47
+ const total = data.data?.total || pageAvatars.length;
48
+ console.log(`[Avatars] ${endpoint} page ${page}: ${pageAvatars.length} avatars (total: ${total})`);
49
+
50
+ // Check if we've fetched all
51
+ if (allAvatars.length >= total || pageAvatars.length === 0) {
52
+ break;
53
+ }
54
+ page++;
55
+ }
56
+
57
+ return allAvatars;
58
+ }
59
+
60
+ export async function GET() {
61
+ if (!LIVEAVATAR_API_KEY) {
62
+ return new Response(
63
+ JSON.stringify({
64
+ error: "LiveAvatar API key not configured",
65
+ customAvatars: [],
66
+ publicAvatars: [],
67
+ }),
68
+ { status: 200, headers: { "Content-Type": "application/json" } }
69
+ );
70
+ }
71
+
72
+ try {
73
+ // Fetch both custom (user) avatars and public avatars in parallel
74
+ const [customAvatars, publicAvatars] = await Promise.all([
75
+ fetchAllAvatars("/v1/avatars"), // User's custom avatars
76
+ fetchAllAvatars("/v1/avatars/public"), // Public avatars
77
+ ]);
78
+
79
+ // Mark custom avatars
80
+ const markedCustomAvatars = customAvatars.map(avatar => ({
81
+ ...avatar,
82
+ is_custom: true,
83
+ }));
84
+
85
+ // Mark public avatars (they don't expire)
86
+ const markedPublicAvatars = publicAvatars.map(avatar => ({
87
+ ...avatar,
88
+ is_custom: false,
89
+ is_expired: false, // Public avatars don't expire
90
+ }));
91
+
92
+ console.log(`[Avatars] Total: ${markedCustomAvatars.length} custom, ${markedPublicAvatars.length} public`);
93
+
94
+ return new Response(
95
+ JSON.stringify({
96
+ customAvatars: markedCustomAvatars,
97
+ publicAvatars: markedPublicAvatars,
98
+ }),
99
+ {
100
+ status: 200,
101
+ headers: { "Content-Type": "application/json" },
102
+ }
103
+ );
104
+ } catch (error) {
105
+ console.error("Error fetching avatars:", error);
106
+ return new Response(
107
+ JSON.stringify({
108
+ customAvatars: [],
109
+ publicAvatars: [],
110
+ }),
111
+ {
112
+ status: 200,
113
+ headers: { "Content-Type": "application/json" },
114
+ }
115
+ );
116
+ }
117
+ }
@@ -0,0 +1,95 @@
1
+ import {
2
+ LIVEAVATAR_API_KEY,
3
+ LIVEAVATAR_API_URL,
4
+ DEFAULT_AVATAR_ID,
5
+ DEFAULT_VOICE_ID,
6
+ IS_SANDBOX,
7
+ LANGUAGE,
8
+ } from "../config";
9
+
10
+ export async function POST(request: Request) {
11
+ let session_token = "";
12
+ let session_id = "";
13
+
14
+ // Allow dynamic parameters from request body, fall back to defaults
15
+ let avatarId = DEFAULT_AVATAR_ID;
16
+ let voiceId = DEFAULT_VOICE_ID;
17
+
18
+ try {
19
+ const body = await request.json();
20
+ if (body.avatarId) {
21
+ avatarId = body.avatarId;
22
+ }
23
+ if (body.voiceId) {
24
+ voiceId = body.voiceId;
25
+ }
26
+ } catch {
27
+ // No body or invalid JSON, use defaults
28
+ }
29
+
30
+ // Check for API key
31
+ if (!LIVEAVATAR_API_KEY) {
32
+ return new Response(
33
+ JSON.stringify({
34
+ error:
35
+ "LiveAvatar API key not configured. Please set LIVEAVATAR_API_KEY in your .env.local file.",
36
+ }),
37
+ { status: 500, headers: { "Content-Type": "application/json" } }
38
+ );
39
+ }
40
+
41
+ try {
42
+ // For OpenClaw integration, we use FULL mode but without a predefined context
43
+ // The avatar will act as a passthrough - we'll control what it says via the bridge
44
+ const res = await fetch(`${LIVEAVATAR_API_URL}/v1/sessions/token`, {
45
+ method: "POST",
46
+ headers: {
47
+ "X-API-KEY": LIVEAVATAR_API_KEY,
48
+ "Content-Type": "application/json",
49
+ },
50
+ body: JSON.stringify({
51
+ mode: "FULL",
52
+ avatar_id: avatarId,
53
+ avatar_persona: {
54
+ // Only include voice_id if provided
55
+ ...(voiceId ? { voice_id: voiceId } : {}),
56
+ // No context_id - we'll use OpenClaw for the intelligence
57
+ language: LANGUAGE,
58
+ },
59
+ is_sandbox: IS_SANDBOX,
60
+ }),
61
+ });
62
+
63
+ if (!res.ok) {
64
+ const resp = await res.json();
65
+ const errorMessage =
66
+ resp.data?.[0]?.message ?? "Failed to retrieve session token";
67
+ return new Response(JSON.stringify({ error: errorMessage }), {
68
+ status: res.status,
69
+ headers: { "Content-Type": "application/json" },
70
+ });
71
+ }
72
+
73
+ const data = await res.json();
74
+ session_token = data.data.session_token;
75
+ session_id = data.data.session_id;
76
+ } catch (error) {
77
+ console.error("Error retrieving session token:", error);
78
+ return new Response(JSON.stringify({ error: (error as Error).message }), {
79
+ status: 500,
80
+ headers: { "Content-Type": "application/json" },
81
+ });
82
+ }
83
+
84
+ if (!session_token) {
85
+ return new Response(
86
+ JSON.stringify({ error: "Failed to retrieve session token" }),
87
+ { status: 500, headers: { "Content-Type": "application/json" } }
88
+ );
89
+ }
90
+
91
+ return new Response(JSON.stringify({ session_token, session_id }), {
92
+ status: 200,
93
+ headers: { "Content-Type": "application/json" },
94
+ });
95
+ }
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
package/app/layout.tsx ADDED
@@ -0,0 +1,37 @@
1
+ import "./globals.css";
2
+ import type { Metadata } from "next";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "OpenClaw LiveAvatar",
6
+ description: "Talk to your OpenClaw agent face-to-face with a real-time AI avatar",
7
+ };
8
+
9
+ // Gateway configuration from environment variables
10
+ const gatewayConfig = {
11
+ url: process.env.OPENCLAW_GATEWAY_URL || "ws://127.0.0.1:18789",
12
+ token: process.env.OPENCLAW_GATEWAY_TOKEN || "",
13
+ };
14
+
15
+ export default function RootLayout({
16
+ children,
17
+ }: {
18
+ children: React.ReactNode;
19
+ }) {
20
+ return (
21
+ <html lang="en">
22
+ <head>
23
+ <script
24
+ dangerouslySetInnerHTML={{
25
+ __html: `
26
+ window.__OPENCLAW_GATEWAY_URL = ${JSON.stringify(gatewayConfig.url)};
27
+ window.__OPENCLAW_GATEWAY_TOKEN = ${JSON.stringify(gatewayConfig.token)};
28
+ `,
29
+ }}
30
+ />
31
+ </head>
32
+ <body className="bg-gray-950 text-white min-h-screen">
33
+ {children}
34
+ </body>
35
+ </html>
36
+ );
37
+ }
package/app/page.tsx ADDED
@@ -0,0 +1,9 @@
1
+ import { OpenClawDemo } from "../src/components/OpenClawDemo";
2
+
3
+ export default function Home() {
4
+ return (
5
+ <main className="min-h-screen flex items-center justify-center">
6
+ <OpenClawDemo />
7
+ </main>
8
+ );
9
+ }
package/bin/cli.js ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'child_process';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname, join } from 'path';
6
+ import open from 'open';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const projectRoot = join(__dirname, '..');
11
+
12
+ const PORT = process.env.PORT || 3001;
13
+
14
+ console.log('');
15
+ console.log(' \x1b[38;5;208m\x1b[1m🦞 OpenClaw LiveAvatar\x1b[0m');
16
+ console.log(' ─────────────────────────────────────');
17
+ console.log('');
18
+
19
+ // Check for LIVEAVATAR_API_KEY
20
+ if (!process.env.LIVEAVATAR_API_KEY) {
21
+ console.log(' \x1b[33m⚠️ Warning: LIVEAVATAR_API_KEY not set\x1b[0m');
22
+ console.log('');
23
+ console.log(' To use LiveAvatar, you need an API key:');
24
+ console.log(' 1. Get your free key at \x1b[36mhttps://app.liveavatar.com\x1b[0m');
25
+ console.log(' 2. Set it: \x1b[90mexport LIVEAVATAR_API_KEY=your_key_here\x1b[0m');
26
+ console.log('');
27
+ }
28
+
29
+ console.log(` Starting server on port ${PORT}...`);
30
+ console.log('');
31
+
32
+ // Start the Next.js server
33
+ const server = spawn('npx', ['next', 'start', '-p', String(PORT)], {
34
+ cwd: projectRoot,
35
+ stdio: ['inherit', 'pipe', 'pipe'],
36
+ env: { ...process.env, PORT: String(PORT) }
37
+ });
38
+
39
+ let serverReady = false;
40
+
41
+ server.stdout.on('data', (data) => {
42
+ const output = data.toString();
43
+
44
+ // Detect when server is ready
45
+ if (!serverReady && (output.includes('Ready') || output.includes('started') || output.includes('localhost'))) {
46
+ serverReady = true;
47
+ const url = `http://localhost:${PORT}`;
48
+
49
+ console.log(` \x1b[32m✓ Server ready!\x1b[0m`);
50
+ console.log('');
51
+ console.log(` \x1b[1mOpen in browser:\x1b[0m \x1b[36m${url}\x1b[0m`);
52
+ console.log('');
53
+ console.log(' \x1b[90mThe LiveAvatar interface will connect to your\x1b[0m');
54
+ console.log(' \x1b[90mOpenClaw Gateway on port 18789 automatically.\x1b[0m');
55
+ console.log('');
56
+ console.log(' \x1b[90mPress Ctrl+C to stop\x1b[0m');
57
+ console.log('');
58
+
59
+ // Open browser
60
+ open(url).catch(() => {
61
+ console.log(' \x1b[90m(Could not open browser automatically)\x1b[0m');
62
+ });
63
+ }
64
+ });
65
+
66
+ server.stderr.on('data', (data) => {
67
+ const output = data.toString();
68
+ // Filter out noisy Next.js output
69
+ if (!output.includes('Compiling') && !output.includes('compiled')) {
70
+ process.stderr.write(data);
71
+ }
72
+ });
73
+
74
+ server.on('error', (err) => {
75
+ console.error(' \x1b[31m✗ Failed to start server:\x1b[0m', err.message);
76
+ console.log('');
77
+ console.log(' Make sure you have run:');
78
+ console.log(' \x1b[90mnpm install\x1b[0m');
79
+ console.log('');
80
+ process.exit(1);
81
+ });
82
+
83
+ server.on('close', (code) => {
84
+ if (code !== 0 && code !== null) {
85
+ console.log('');
86
+ console.log(` \x1b[31mServer exited with code ${code}\x1b[0m`);
87
+ }
88
+ process.exit(code || 0);
89
+ });
90
+
91
+ // Handle graceful shutdown
92
+ process.on('SIGINT', () => {
93
+ console.log('');
94
+ console.log(' \x1b[90mShutting down...\x1b[0m');
95
+ server.kill('SIGINT');
96
+ });
97
+
98
+ process.on('SIGTERM', () => {
99
+ server.kill('SIGTERM');
100
+ });
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "openclaw-liveavatar",
3
+ "version": "1.0.0",
4
+ "description": "Real-time video avatar chat interface for OpenClaw - talk face-to-face with your AI agent",
5
+ "type": "module",
6
+ "bin": {
7
+ "openclaw-liveavatar": "./bin/cli.js"
8
+ },
9
+ "scripts": {
10
+ "dev": "next dev -p 3001 --turbopack",
11
+ "build": "next build",
12
+ "start": "next start -p 3001",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "openclaw",
17
+ "liveavatar",
18
+ "heygen",
19
+ "ai",
20
+ "avatar",
21
+ "voice",
22
+ "chat",
23
+ "video",
24
+ "assistant"
25
+ ],
26
+ "author": "eNNNo",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/eNNNo/openclaw-liveavatar.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/eNNNo/openclaw-liveavatar/issues"
34
+ },
35
+ "homepage": "https://github.com/eNNNo/openclaw-liveavatar#readme",
36
+ "files": [
37
+ "bin",
38
+ "app",
39
+ "src",
40
+ "public",
41
+ ".next",
42
+ "next.config.ts",
43
+ "tailwind.config.ts",
44
+ "postcss.config.mjs",
45
+ "tsconfig.json"
46
+ ],
47
+ "dependencies": {
48
+ "@heygen/liveavatar-web-sdk": "^0.0.10",
49
+ "next": "^15.4.2",
50
+ "open": "^10.1.0",
51
+ "react": "^19.1.0",
52
+ "react-dom": "^19.1.0"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "22.14.0",
56
+ "@types/react": "19.1.6",
57
+ "@types/react-dom": "19.1.6",
58
+ "autoprefixer": "^10.4.21",
59
+ "postcss": "^8.5.3",
60
+ "tailwindcss": "^3.4.17",
61
+ "typescript": "5.8.2"
62
+ },
63
+ "engines": {
64
+ "node": ">=18.0.0"
65
+ }
66
+ }