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
@@ -0,0 +1,399 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useCallback, useRef } from "react";
4
+ import { LiveAvatarSession } from "./LiveAvatarSession";
5
+ import { getGatewayClient } from "../gateway/client";
6
+ import { GatewayConnectionState } from "../gateway/types";
7
+
8
+ type SessionState = "idle" | "connecting" | "session" | "ended" | "error";
9
+
10
+ interface Avatar {
11
+ id: string;
12
+ name: string;
13
+ preview_url?: string;
14
+ is_expired?: boolean;
15
+ is_custom?: boolean;
16
+ }
17
+
18
+ // Collapsible section component for avatar groups
19
+ const AvatarSection = ({
20
+ title,
21
+ avatars,
22
+ isExpanded,
23
+ onToggle,
24
+ onSelectAvatar,
25
+ badge,
26
+ showExpiredBadge = true,
27
+ }: {
28
+ title: string;
29
+ avatars: Avatar[];
30
+ isExpanded: boolean;
31
+ onToggle: () => void;
32
+ onSelectAvatar: (id: string) => void;
33
+ badge?: { text: string; color: string };
34
+ showExpiredBadge?: boolean;
35
+ }) => {
36
+ // For custom avatars, count non-expired; for public avatars, all are active
37
+ const expiredCount = avatars.filter(a => a.is_expired === true).length;
38
+ const activeCount = avatars.length - expiredCount;
39
+
40
+ return (
41
+ <div className="w-full">
42
+ <button
43
+ onClick={onToggle}
44
+ className="w-full flex items-center justify-between px-3 py-2 bg-gray-800/50 hover:bg-gray-800/70 rounded-lg transition-colors"
45
+ >
46
+ <div className="flex items-center gap-2">
47
+ <svg
48
+ className={`w-4 h-4 text-gray-400 transition-transform ${isExpanded ? "rotate-90" : ""}`}
49
+ fill="none"
50
+ stroke="currentColor"
51
+ viewBox="0 0 24 24"
52
+ >
53
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
54
+ </svg>
55
+ <span className="text-sm font-medium text-white">{title}</span>
56
+ {badge && (
57
+ <span className={`text-[10px] px-1.5 py-0.5 rounded ${badge.color}`}>
58
+ {badge.text}
59
+ </span>
60
+ )}
61
+ </div>
62
+ <span className="text-xs text-gray-500">
63
+ {expiredCount > 0 ? `${activeCount} active / ${avatars.length} total` : `${avatars.length} available`}
64
+ </span>
65
+ </button>
66
+
67
+ {isExpanded && avatars.length > 0 && (
68
+ <div className="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-3 mt-3 px-1">
69
+ {avatars.map((avatar) => {
70
+ const isExpired = avatar.is_expired === true;
71
+ return (
72
+ <button
73
+ key={avatar.id}
74
+ onClick={() => onSelectAvatar(avatar.id)}
75
+ disabled={isExpired}
76
+ className={`relative aspect-square rounded-lg overflow-hidden border-2 transition-all group ${
77
+ isExpired
78
+ ? "border-red-500/30 opacity-50 cursor-not-allowed"
79
+ : "border-white/10 hover:border-orange-500/50 hover:scale-105"
80
+ }`}
81
+ >
82
+ {avatar.preview_url ? (
83
+ <img
84
+ src={avatar.preview_url}
85
+ alt={avatar.name}
86
+ className="w-full h-full object-cover"
87
+ />
88
+ ) : (
89
+ <div className="w-full h-full bg-gray-700 flex items-center justify-center">
90
+ <span className="text-gray-400 text-xs text-center px-1">
91
+ {avatar.name.slice(0, 10)}
92
+ </span>
93
+ </div>
94
+ )}
95
+ <div className="absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-end">
96
+ <div className="w-full p-2 bg-gradient-to-t from-black/80 to-transparent opacity-0 group-hover:opacity-100 transition-opacity">
97
+ <span className="text-white text-xs truncate block">{avatar.name}</span>
98
+ </div>
99
+ </div>
100
+ {showExpiredBadge && isExpired && (
101
+ <div className="absolute top-1 left-1 bg-red-500 text-white text-[10px] px-1.5 py-0.5 rounded">
102
+ Expired
103
+ </div>
104
+ )}
105
+ </button>
106
+ );
107
+ })}
108
+ </div>
109
+ )}
110
+
111
+ {isExpanded && avatars.length === 0 && (
112
+ <div className="text-center py-6 text-gray-500 text-sm">
113
+ No {title.toLowerCase()} available
114
+ </div>
115
+ )}
116
+ </div>
117
+ );
118
+ };
119
+
120
+ export const OpenClawDemo = () => {
121
+ const [sessionToken, setSessionToken] = useState("");
122
+ const [error, setError] = useState<string | null>(null);
123
+ const [sessionState, setSessionState] = useState<SessionState>("connecting"); // Start connecting immediately
124
+ const [customAvatars, setCustomAvatars] = useState<Avatar[]>([]);
125
+ const [publicAvatars, setPublicAvatars] = useState<Avatar[]>([]);
126
+ const [loadingAvatars, setLoadingAvatars] = useState(true);
127
+ const [gatewayState, setGatewayState] = useState<GatewayConnectionState>("disconnected");
128
+ const [customExpanded, setCustomExpanded] = useState(false); // Start collapsed since likely expired
129
+ const [publicExpanded, setPublicExpanded] = useState(true); // Start expanded
130
+ const hasAutoStartedRef = useRef(false);
131
+
132
+ // Start a session with an optional specific avatar
133
+ const startSessionWithAvatar = useCallback(async (avatarId?: string) => {
134
+ setError(null);
135
+ setSessionState("connecting");
136
+
137
+ try {
138
+ const sessionRes = await fetch("/api/start-session", {
139
+ method: "POST",
140
+ headers: { "Content-Type": "application/json" },
141
+ body: JSON.stringify(avatarId ? { avatarId } : {}),
142
+ });
143
+
144
+ if (!sessionRes.ok) {
145
+ const errorData = await sessionRes.json();
146
+ throw new Error(errorData.error || "Failed to start session");
147
+ }
148
+
149
+ const { session_token } = await sessionRes.json();
150
+ setSessionToken(session_token);
151
+ setSessionState("session");
152
+ } catch (err: unknown) {
153
+ setError((err as Error).message);
154
+ setSessionState("error");
155
+ }
156
+ }, []);
157
+
158
+ // Fetch avatars and connect to gateway on mount
159
+ useEffect(() => {
160
+ // Fetch available avatars and auto-start with first available
161
+ const fetchAvatarsAndAutoStart = async () => {
162
+ setLoadingAvatars(true);
163
+ try {
164
+ const res = await fetch("/api/get-avatars");
165
+ if (res.ok) {
166
+ const data = await res.json();
167
+ const custom: Avatar[] = data.customAvatars || [];
168
+ const publicList: Avatar[] = data.publicAvatars || [];
169
+ setCustomAvatars(custom);
170
+ setPublicAvatars(publicList);
171
+
172
+ // Auto-start with first available avatar (prefer active custom, then public)
173
+ if (!hasAutoStartedRef.current) {
174
+ hasAutoStartedRef.current = true;
175
+ const firstActiveCustom = custom.find(a => !a.is_expired);
176
+ const firstPublic = publicList[0];
177
+ const firstAvatar = firstActiveCustom || firstPublic;
178
+
179
+ if (firstAvatar) {
180
+ console.log("[AutoStart] Starting session with avatar:", firstAvatar.name);
181
+ startSessionWithAvatar(firstAvatar.id);
182
+ } else {
183
+ // No avatars available, go to selection screen
184
+ setSessionState("ended");
185
+ }
186
+ }
187
+ } else {
188
+ setSessionState("error");
189
+ setError("Failed to fetch avatars");
190
+ }
191
+ } catch (err) {
192
+ console.error("Failed to fetch avatars:", err);
193
+ setSessionState("error");
194
+ setError("Failed to connect to avatar service");
195
+ } finally {
196
+ setLoadingAvatars(false);
197
+ }
198
+ };
199
+ fetchAvatarsAndAutoStart();
200
+
201
+ // Connect to OpenClaw gateway
202
+ const gateway = getGatewayClient();
203
+ gateway.onConnectionState(setGatewayState);
204
+ gateway.connect().catch((err) => {
205
+ console.log("[OpenClaw] Gateway not available:", err.message);
206
+ });
207
+
208
+ return () => {
209
+ gateway.offConnectionState(setGatewayState);
210
+ };
211
+ }, [startSessionWithAvatar]);
212
+
213
+ const onSessionStopped = useCallback(() => {
214
+ setSessionToken("");
215
+ setSessionState("ended");
216
+ }, []);
217
+
218
+ // Connecting screen (shown immediately on load)
219
+ if (sessionState === "connecting") {
220
+ return (
221
+ <div className="w-full h-full flex flex-col items-center justify-center gap-6 p-4">
222
+ <div className="flex flex-col items-center gap-4">
223
+ <div className="relative">
224
+ <div className="w-16 h-16 border-4 border-orange-500/30 rounded-full"></div>
225
+ <div className="absolute top-0 left-0 w-16 h-16 border-4 border-orange-500 rounded-full border-t-transparent animate-spin"></div>
226
+ </div>
227
+
228
+ <div className="text-center">
229
+ <div className="flex items-center justify-center gap-3 mb-4">
230
+ <div className="w-10 h-10 bg-gradient-to-br from-orange-500 to-red-600 rounded-xl flex items-center justify-center">
231
+ <span className="text-xl">🦞</span>
232
+ </div>
233
+ <h1 className="text-2xl font-bold text-white">
234
+ OpenClaw LiveAvatar
235
+ </h1>
236
+ </div>
237
+ <p className="text-gray-400 text-sm">
238
+ Connecting to your AI avatar...
239
+ </p>
240
+ </div>
241
+ </div>
242
+ </div>
243
+ );
244
+ }
245
+
246
+ // Error screen
247
+ if (sessionState === "error") {
248
+ return (
249
+ <div className="w-full h-full flex flex-col items-center justify-center gap-6 p-4">
250
+ <div className="w-full max-w-md flex flex-col items-center gap-6">
251
+ <div className="text-center">
252
+ <div className="w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
253
+ <svg
254
+ className="w-8 h-8 text-red-500"
255
+ fill="none"
256
+ stroke="currentColor"
257
+ viewBox="0 0 24 24"
258
+ >
259
+ <path
260
+ strokeLinecap="round"
261
+ strokeLinejoin="round"
262
+ strokeWidth={2}
263
+ d="M6 18L18 6M6 6l12 12"
264
+ />
265
+ </svg>
266
+ </div>
267
+ <h1 className="text-2xl font-bold text-white mb-2">
268
+ Connection Failed
269
+ </h1>
270
+ <p className="text-gray-400 mb-4">
271
+ Could not connect to the LiveAvatar service.
272
+ </p>
273
+ {error && (
274
+ <div className="text-red-400 bg-red-900/30 px-4 py-3 rounded-lg text-sm mb-4">
275
+ {error}
276
+ </div>
277
+ )}
278
+ </div>
279
+
280
+ <button
281
+ onClick={() => setSessionState("idle")}
282
+ className="px-6 py-2 bg-gradient-to-r from-orange-500 to-red-600 hover:from-orange-600 hover:to-red-700 text-white rounded-lg transition-all"
283
+ >
284
+ Back to Avatar Selection
285
+ </button>
286
+
287
+ <p className="text-xs text-gray-500 text-center">
288
+ Make sure your LIVEAVATAR_API_KEY is configured in .env.local
289
+ </p>
290
+ </div>
291
+ </div>
292
+ );
293
+ }
294
+
295
+ // Avatar selection screen (only shown after session ends)
296
+ if (sessionState === "ended") {
297
+ return (
298
+ <div className="w-full h-full flex flex-col items-center justify-center gap-6 p-4">
299
+ <div className="w-full max-w-2xl flex flex-col items-center gap-6">
300
+ {/* Header */}
301
+ <div className="text-center">
302
+ <div className="flex items-center justify-center gap-3 mb-4">
303
+ <div className="w-10 h-10 bg-gradient-to-br from-orange-500 to-red-600 rounded-xl flex items-center justify-center">
304
+ <span className="text-xl">🦞</span>
305
+ </div>
306
+ <h1 className="text-2xl font-bold text-white">
307
+ OpenClaw LiveAvatar
308
+ </h1>
309
+ </div>
310
+ <p className="text-gray-400 mb-2">Session ended. Select an avatar to start a new conversation.</p>
311
+ </div>
312
+
313
+ {/* OpenClaw Connection Status */}
314
+ <div className="flex items-center gap-2 px-4 py-2 bg-gray-800/50 rounded-lg">
315
+ <div className={`w-2 h-2 rounded-full ${
316
+ gatewayState === "connected" ? "bg-green-500" :
317
+ gatewayState === "connecting" ? "bg-yellow-500 animate-pulse" :
318
+ "bg-gray-500"
319
+ }`} />
320
+ <span className="text-sm text-gray-300">
321
+ {gatewayState === "connected" ? "OpenClaw Connected" :
322
+ gatewayState === "connecting" ? "Connecting to OpenClaw..." :
323
+ "OpenClaw Disconnected"}
324
+ </span>
325
+ </div>
326
+
327
+ {/* Avatar Lists */}
328
+ <div className="w-full space-y-4">
329
+ <h2 className="text-lg font-medium text-white">Select an Avatar</h2>
330
+ {loadingAvatars ? (
331
+ <div className="flex items-center justify-center py-12">
332
+ <div className="w-8 h-8 border-2 border-orange-500/30 border-t-orange-500 rounded-full animate-spin" />
333
+ </div>
334
+ ) : customAvatars.length === 0 && publicAvatars.length === 0 ? (
335
+ <div className="text-center py-12 text-gray-400">
336
+ No avatars available. Check your LiveAvatar API key.
337
+ </div>
338
+ ) : (
339
+ <div className="space-y-4">
340
+ {/* Custom Avatars Section */}
341
+ {customAvatars.length > 0 && (
342
+ <AvatarSection
343
+ title="Custom Avatars"
344
+ avatars={customAvatars}
345
+ isExpanded={customExpanded}
346
+ onToggle={() => setCustomExpanded(!customExpanded)}
347
+ onSelectAvatar={startSessionWithAvatar}
348
+ />
349
+ )}
350
+
351
+ {/* Public Avatars Section */}
352
+ {publicAvatars.length > 0 && (
353
+ <AvatarSection
354
+ title="Public Avatars"
355
+ avatars={publicAvatars}
356
+ isExpanded={publicExpanded}
357
+ onToggle={() => setPublicExpanded(!publicExpanded)}
358
+ onSelectAvatar={startSessionWithAvatar}
359
+ showExpiredBadge={false}
360
+ />
361
+ )}
362
+ </div>
363
+ )}
364
+ </div>
365
+
366
+ {/* Footer */}
367
+ <p className="text-xs text-gray-500 text-center">
368
+ Powered by{" "}
369
+ <a
370
+ href="https://liveavatar.com"
371
+ target="_blank"
372
+ rel="noopener noreferrer"
373
+ className="text-blue-400 hover:underline"
374
+ >
375
+ LiveAvatar
376
+ </a>{" "}
377
+ +{" "}
378
+ <a
379
+ href="https://openclaw.ai"
380
+ target="_blank"
381
+ rel="noopener noreferrer"
382
+ className="text-orange-400 hover:underline"
383
+ >
384
+ OpenClaw
385
+ </a>
386
+ </p>
387
+ </div>
388
+ </div>
389
+ );
390
+ }
391
+
392
+ // Session screen
393
+ return (
394
+ <LiveAvatarSession
395
+ sessionAccessToken={sessionToken}
396
+ onSessionStopped={onSessionStopped}
397
+ />
398
+ );
399
+ };