@alia.onl/sdk 1.0.0 → 3.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 (44) hide show
  1. package/package.json +37 -3
  2. package/src/components/AliaChatContent.tsx +313 -0
  3. package/src/components/AliaChatInput.tsx +174 -27
  4. package/src/components/AliaChatMessageList.tsx +220 -34
  5. package/src/components/AliaChatScreen.tsx +132 -0
  6. package/src/components/AliaChatSheet.tsx +36 -49
  7. package/src/components/AliaChatSuggestions.tsx +1 -1
  8. package/src/components/AliaFace.tsx +402 -0
  9. package/src/components/Markdown.tsx +288 -0
  10. package/src/components/Reasoning.tsx +154 -0
  11. package/src/components/ThinkingIndicator.tsx +134 -0
  12. package/src/components/ui/button.tsx +104 -0
  13. package/src/components/ui/chat-text-input.tsx +164 -0
  14. package/src/components/ui/dropdown-menu.tsx +2 -0
  15. package/src/components/ui/dropdown-menu.web.tsx +355 -0
  16. package/src/components/ui/prompt-input/actions.tsx +28 -0
  17. package/src/components/ui/prompt-input/add-menu.tsx +83 -0
  18. package/src/components/ui/prompt-input/attachments.tsx +183 -0
  19. package/src/components/ui/prompt-input/autocomplete.tsx +168 -0
  20. package/src/components/ui/prompt-input/context.tsx +71 -0
  21. package/src/components/ui/prompt-input/mic-button.tsx +53 -0
  22. package/src/components/ui/prompt-input/prompt-input.tsx +276 -0
  23. package/src/components/ui/prompt-input/submit-button.tsx +49 -0
  24. package/src/components/ui/prompt-input/textarea.tsx +64 -0
  25. package/src/components/ui/text.tsx +29 -0
  26. package/src/components/voice/AudioWaveVisualizer.tsx +377 -0
  27. package/src/components/voice/VoiceControls.tsx +180 -0
  28. package/src/components/voice/VoiceOverlay.tsx +43 -0
  29. package/src/hooks/useAliaChat.ts +278 -91
  30. package/src/hooks/useAudioLevelMonitor.ts +142 -0
  31. package/src/hooks/useAudioLevels.ts +51 -0
  32. package/src/hooks/useDocumentPicker.ts +37 -0
  33. package/src/hooks/useImagePicker.ts +38 -0
  34. package/src/hooks/useSoundEffects.ts +128 -0
  35. package/src/hooks/useSpeechToText.ts +182 -0
  36. package/src/hooks/useTTS.ts +233 -0
  37. package/src/hooks/useVoiceRoom.ts +420 -0
  38. package/src/index.ts +71 -6
  39. package/src/lib/keyboard.native.tsx +6 -0
  40. package/src/lib/keyboard.tsx +25 -0
  41. package/src/lib/utils.ts +14 -0
  42. package/src/optional-deps.d.ts +86 -0
  43. package/src/theme.ts +25 -2
  44. package/src/types.ts +63 -1
@@ -0,0 +1,29 @@
1
+ import * as React from "react";
2
+ import { Text as RNText, type TextProps as RNTextProps } from "react-native";
3
+ import { cn } from "../../lib/utils";
4
+
5
+ const TextClassContext = React.createContext<string | undefined>(undefined);
6
+
7
+ export type TextProps = RNTextProps & {
8
+ className?: string;
9
+ };
10
+
11
+ const Text = React.forwardRef<RNText, TextProps>(
12
+ ({ className, ...props }, ref) => {
13
+ const textClass = React.useContext(TextClassContext);
14
+ return (
15
+ <RNText
16
+ className={cn(
17
+ "text-base leading-7 text-foreground web:select-text font-sans",
18
+ textClass,
19
+ className
20
+ )}
21
+ ref={ref}
22
+ {...props}
23
+ />
24
+ );
25
+ }
26
+ );
27
+ Text.displayName = "Text";
28
+
29
+ export { Text, TextClassContext };
@@ -0,0 +1,377 @@
1
+ import React, { useEffect } from 'react';
2
+ import { View, Platform, useWindowDimensions } from 'react-native';
3
+ import Svg, { Path } from 'react-native-svg';
4
+ import Animated, {
5
+ useSharedValue,
6
+ useAnimatedProps,
7
+ useAnimatedStyle,
8
+ withTiming,
9
+ withRepeat,
10
+ withSequence,
11
+ Easing,
12
+ type SharedValue,
13
+ } from 'react-native-reanimated';
14
+ import type { AgentState } from '../../types';
15
+
16
+ const AnimatedPath = Animated.createAnimatedComponent(Path);
17
+
18
+ const GLOW_HEIGHT = 350;
19
+ const NUM_POINTS = 8;
20
+
21
+ // Ocean-style wave layers: fill from bottom to a surface level, crests undulate
22
+ interface WaveLayerConfig {
23
+ color: string;
24
+ surfaceY: number; // surface level as % from top (50 = middle)
25
+ waveHeight: number; // base crest height in px
26
+ maxWaveBoost: number; // extra crest height from audio
27
+ phaseOffset: number;
28
+ speed: number; // ms for one full horizontal cycle
29
+ blur: number;
30
+ }
31
+
32
+ const WAVE_LAYERS: WaveLayerConfig[] = [
33
+ // Deepest layer - dark blue, highest surface (closest to top)
34
+ {
35
+ color: 'rgba(12, 74, 110, 0.8)',
36
+ surfaceY: 35,
37
+ waveHeight: 14,
38
+ maxWaveBoost: 120,
39
+ phaseOffset: 0,
40
+ speed: 7000,
41
+ blur: 30,
42
+ },
43
+ // Mid layer - ocean blue
44
+ {
45
+ color: 'rgba(14, 165, 233, 0.45)',
46
+ surfaceY: 42,
47
+ waveHeight: 16,
48
+ maxWaveBoost: 140,
49
+ phaseOffset: Math.PI * 0.8,
50
+ speed: 5500,
51
+ blur: 22,
52
+ },
53
+ // Front layer - bright cyan, lowest surface
54
+ {
55
+ color: 'rgba(56, 189, 248, 0.35)',
56
+ surfaceY: 50,
57
+ waveHeight: 20,
58
+ maxWaveBoost: 150,
59
+ phaseOffset: Math.PI * 1.5,
60
+ speed: 4500,
61
+ blur: 16,
62
+ },
63
+ // Accent layer - indigo/purple
64
+ {
65
+ color: 'rgba(99, 102, 241, 0.25)',
66
+ surfaceY: 38,
67
+ waveHeight: 12,
68
+ maxWaveBoost: 100,
69
+ phaseOffset: Math.PI * 0.3,
70
+ speed: 8000,
71
+ blur: 28,
72
+ },
73
+ ];
74
+
75
+ // Background glow blobs for ambient color
76
+ interface BlobConfig {
77
+ color: string;
78
+ baseWidth: number;
79
+ baseHeight: number;
80
+ x: number;
81
+ y: number;
82
+ blur: number;
83
+ amplitudeScale: number;
84
+ phaseSpeed: number;
85
+ phaseDirection: 1 | -1;
86
+ }
87
+
88
+ const BLOBS: BlobConfig[] = [
89
+ {
90
+ color: '#0c4a6e',
91
+ baseWidth: 320,
92
+ baseHeight: 200,
93
+ x: 50,
94
+ y: 55,
95
+ blur: 80,
96
+ amplitudeScale: 1.0,
97
+ phaseSpeed: 4000,
98
+ phaseDirection: 1,
99
+ },
100
+ {
101
+ color: '#0ea5e9',
102
+ baseWidth: 220,
103
+ baseHeight: 140,
104
+ x: 38,
105
+ y: 65,
106
+ blur: 60,
107
+ amplitudeScale: 1.2,
108
+ phaseSpeed: 3500,
109
+ phaseDirection: -1,
110
+ },
111
+ {
112
+ color: '#6366f1',
113
+ baseWidth: 180,
114
+ baseHeight: 160,
115
+ x: 70,
116
+ y: 55,
117
+ blur: 70,
118
+ amplitudeScale: 0.7,
119
+ phaseSpeed: 4500,
120
+ phaseDirection: -1,
121
+ },
122
+ {
123
+ color: '#a855f7',
124
+ baseWidth: 130,
125
+ baseHeight: 110,
126
+ x: 78,
127
+ y: 65,
128
+ blur: 60,
129
+ amplitudeScale: 0.5,
130
+ phaseSpeed: 5500,
131
+ phaseDirection: 1,
132
+ },
133
+ ];
134
+
135
+ // Colors per agent state for waves — speaking blends blue + purple
136
+ const WAVE_COLORS: Record<AgentState, WaveLayerConfig['color'][]> = {
137
+ idle: [
138
+ 'rgba(12, 74, 110, 0.8)',
139
+ 'rgba(14, 165, 233, 0.45)',
140
+ 'rgba(56, 189, 248, 0.35)',
141
+ 'rgba(99, 102, 241, 0.25)',
142
+ ],
143
+ listening: [
144
+ 'rgba(12, 74, 110, 0.8)',
145
+ 'rgba(14, 165, 233, 0.45)',
146
+ 'rgba(56, 189, 248, 0.35)',
147
+ 'rgba(99, 102, 241, 0.25)',
148
+ ],
149
+ thinking: [
150
+ 'rgba(120, 80, 20, 0.75)',
151
+ 'rgba(234, 179, 8, 0.45)',
152
+ 'rgba(251, 191, 36, 0.35)',
153
+ 'rgba(253, 224, 71, 0.25)',
154
+ ],
155
+ speaking: [
156
+ 'rgba(30, 40, 100, 0.8)',
157
+ 'rgba(99, 102, 241, 0.5)',
158
+ 'rgba(147, 51, 234, 0.4)',
159
+ 'rgba(56, 189, 248, 0.3)',
160
+ ],
161
+ };
162
+
163
+ const BLOB_COLORS: Record<AgentState, string[]> = {
164
+ idle: ['#0c4a6e', '#0ea5e9', '#6366f1', '#a855f7'],
165
+ listening: ['#0c4a6e', '#0ea5e9', '#6366f1', '#a855f7'],
166
+ thinking: ['#92400e', '#eab308', '#f59e0b', '#fbbf24'],
167
+ speaking: ['#1e2870', '#6366f1', '#9333ea', '#0ea5e9'],
168
+ };
169
+
170
+ interface AudioWaveVisualizerProps {
171
+ waveAmplitude: SharedValue<number>;
172
+ agentState: AgentState;
173
+ isConnected: boolean;
174
+ }
175
+
176
+ export function AudioWaveVisualizer({
177
+ waveAmplitude,
178
+ agentState = 'idle',
179
+ isConnected,
180
+ }: AudioWaveVisualizerProps) {
181
+ const { width: screenWidth } = useWindowDimensions();
182
+ const state = WAVE_COLORS[agentState] ? agentState : 'idle';
183
+
184
+ return (
185
+ <View
186
+ style={{
187
+ width: screenWidth,
188
+ height: GLOW_HEIGHT,
189
+ position: 'relative',
190
+ }}
191
+ >
192
+ {/* Background glow blobs */}
193
+ {BLOBS.map((blob, i) => (
194
+ <GlowBlob
195
+ key={`blob-${i}`}
196
+ blob={blob}
197
+ color={BLOB_COLORS[state][i]}
198
+ waveAmplitude={waveAmplitude}
199
+ isConnected={isConnected}
200
+ screenWidth={screenWidth}
201
+ />
202
+ ))}
203
+
204
+ {/* Ocean wave layers — filled from surface down to bottom, with blur */}
205
+ {WAVE_LAYERS.map((layer, i) => (
206
+ <OceanWave
207
+ key={`wave-${i}`}
208
+ layer={layer}
209
+ color={WAVE_COLORS[state][i]}
210
+ waveAmplitude={waveAmplitude}
211
+ isConnected={isConnected}
212
+ screenWidth={screenWidth}
213
+ />
214
+ ))}
215
+ </View>
216
+ );
217
+ }
218
+
219
+ // Ocean wave: surface undulates horizontally, filled from surface to bottom
220
+ function OceanWave({
221
+ layer,
222
+ color,
223
+ waveAmplitude,
224
+ isConnected,
225
+ screenWidth,
226
+ }: {
227
+ layer: WaveLayerConfig;
228
+ color: string;
229
+ waveAmplitude: SharedValue<number>;
230
+ isConnected: boolean;
231
+ screenWidth: number;
232
+ }) {
233
+ const phase = useSharedValue(0);
234
+
235
+ useEffect(() => {
236
+ phase.value = 0;
237
+ phase.value = withRepeat(
238
+ withTiming(Math.PI * 2, { duration: layer.speed, easing: Easing.linear }),
239
+ -1,
240
+ false,
241
+ );
242
+ }, []);
243
+
244
+ const animatedProps = useAnimatedProps(() => {
245
+ const amp = waveAmplitude.value;
246
+ // Surface Y position (fixed, doesn't move with audio)
247
+ const surfaceY = (layer.surfaceY / 100) * GLOW_HEIGHT;
248
+ // Wave crest height: base + audio boost
249
+ const crestHeight = layer.waveHeight + amp * layer.maxWaveBoost;
250
+ const currentPhase = phase.value + layer.phaseOffset;
251
+ const segW = screenWidth / NUM_POINTS;
252
+
253
+ // Start at bottom-left corner
254
+ let d = `M 0 ${GLOW_HEIGHT}`;
255
+
256
+ // Up to first surface point
257
+ const firstY = surfaceY + Math.sin(currentPhase) * crestHeight;
258
+ d += ` L 0 ${firstY}`;
259
+
260
+ // Draw undulating surface across the width
261
+ for (let i = 0; i < NUM_POINTS; i++) {
262
+ const x1 = (i + 1) * segW;
263
+ const y0 =
264
+ surfaceY +
265
+ Math.sin(currentPhase + (i / NUM_POINTS) * Math.PI * 2) * crestHeight;
266
+ const y1 =
267
+ surfaceY +
268
+ Math.sin(currentPhase + ((i + 1) / NUM_POINTS) * Math.PI * 2) * crestHeight;
269
+
270
+ const cp1x = i * segW + segW * 0.5;
271
+ const cp2x = x1 - segW * 0.5;
272
+
273
+ d += ` C ${cp1x} ${y0}, ${cp2x} ${y1}, ${x1} ${y1}`;
274
+ }
275
+
276
+ // Close: down to bottom-right, across to bottom-left
277
+ d += ` L ${screenWidth} ${GLOW_HEIGHT} Z`;
278
+
279
+ return { d };
280
+ });
281
+
282
+ const webBlurStyle =
283
+ Platform.OS === 'web'
284
+ ? ({
285
+ filter: `blur(${layer.blur}px)`,
286
+ WebkitFilter: `blur(${layer.blur}px)`,
287
+ } as any)
288
+ : {};
289
+
290
+ return (
291
+ <View
292
+ style={{
293
+ position: 'absolute',
294
+ top: 0,
295
+ left: 0,
296
+ right: 0,
297
+ bottom: 0,
298
+ opacity: isConnected ? 1 : 0,
299
+ ...webBlurStyle,
300
+ pointerEvents: 'none',
301
+ }}
302
+ >
303
+ <Svg width={screenWidth} height={GLOW_HEIGHT}>
304
+ <AnimatedPath animatedProps={animatedProps} fill={color} />
305
+ </Svg>
306
+ </View>
307
+ );
308
+ }
309
+
310
+ // Ambient glow blob
311
+ function GlowBlob({
312
+ blob,
313
+ color,
314
+ waveAmplitude,
315
+ isConnected,
316
+ screenWidth,
317
+ }: {
318
+ blob: BlobConfig;
319
+ color: string;
320
+ waveAmplitude: SharedValue<number>;
321
+ isConnected: boolean;
322
+ screenWidth: number;
323
+ }) {
324
+ const drift = useSharedValue(0);
325
+
326
+ useEffect(() => {
327
+ drift.value = 0;
328
+ drift.value = withRepeat(
329
+ withSequence(
330
+ withTiming(1, {
331
+ duration: blob.phaseSpeed,
332
+ easing: Easing.bezier(0.37, 0, 0.63, 1),
333
+ }),
334
+ withTiming(0, {
335
+ duration: blob.phaseSpeed,
336
+ easing: Easing.bezier(0.37, 0, 0.63, 1),
337
+ }),
338
+ ),
339
+ -1,
340
+ false,
341
+ );
342
+ }, []);
343
+
344
+ const animatedStyle = useAnimatedStyle(() => {
345
+ const amp = waveAmplitude.value;
346
+ const scale = 1 + amp * 0.3 * blob.amplitudeScale;
347
+ const driftOffset = (drift.value - 0.5) * 20 * blob.phaseDirection;
348
+ const opacity = isConnected ? 0.45 + amp * 0.35 * blob.amplitudeScale : 0;
349
+
350
+ const w = blob.baseWidth * scale;
351
+ const h = blob.baseHeight * scale;
352
+ const left = (blob.x / 100) * screenWidth - w / 2 + driftOffset;
353
+ const top = (blob.y / 100) * GLOW_HEIGHT - h / 2;
354
+
355
+ return {
356
+ position: 'absolute' as const,
357
+ width: w,
358
+ height: h,
359
+ left,
360
+ top,
361
+ borderRadius: w / 2,
362
+ backgroundColor: color,
363
+ opacity,
364
+ transform: [{ scale }],
365
+ };
366
+ });
367
+
368
+ const webBlurStyle =
369
+ Platform.OS === 'web'
370
+ ? ({
371
+ filter: `blur(${blob.blur}px)`,
372
+ WebkitFilter: `blur(${blob.blur}px)`,
373
+ } as any)
374
+ : {};
375
+
376
+ return <Animated.View style={[animatedStyle, webBlurStyle]} />;
377
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Bottom voice control bar that replaces PromptInput when voice mode is active.
3
+ * Contains status text, mute/cohost/end buttons, and "Continue" for cohost rounds.
4
+ */
5
+
6
+ import { View, Text, Pressable, ActivityIndicator, StyleSheet } from 'react-native';
7
+ import type { RoomState, AgentState } from '../../types';
8
+ import { useAliaColors } from '../../theme';
9
+
10
+ interface VoiceControlsProps {
11
+ roomState: RoomState;
12
+ agentState: AgentState;
13
+ isMuted: boolean;
14
+ cohostActive: boolean;
15
+ currentSpeaker: 'primary' | 'cohost' | 'user' | null;
16
+ roundComplete: boolean;
17
+ onToggleMute: () => void;
18
+ onEnableCohost: () => void;
19
+ onDisableCohost: () => void;
20
+ onContinueCohost: () => void;
21
+ onEnd: () => void;
22
+ }
23
+
24
+ function getStatusText(
25
+ roomState: RoomState,
26
+ agentState: AgentState,
27
+ isMuted: boolean,
28
+ cohostActive: boolean,
29
+ currentSpeaker: string | null,
30
+ ): string {
31
+ if (roomState === 'connecting') return 'Connecting...';
32
+ if (roomState === 'connected') {
33
+ if (cohostActive && currentSpeaker === 'cohost') return 'Cohost speaking...';
34
+ if (cohostActive && currentSpeaker === 'primary') return 'Alia speaking...';
35
+ if (agentState === 'listening') return isMuted ? 'Muted' : 'Listening...';
36
+ if (agentState === 'thinking') return 'Thinking...';
37
+ if (agentState === 'speaking') return 'Speaking...';
38
+ return 'Connected';
39
+ }
40
+ return '';
41
+ }
42
+
43
+ export function VoiceControls({
44
+ roomState,
45
+ agentState,
46
+ isMuted,
47
+ cohostActive,
48
+ currentSpeaker,
49
+ roundComplete,
50
+ onToggleMute,
51
+ onEnableCohost,
52
+ onDisableCohost,
53
+ onContinueCohost,
54
+ onEnd,
55
+ }: VoiceControlsProps) {
56
+ const colors = useAliaColors();
57
+ const statusText = getStatusText(roomState, agentState, isMuted, cohostActive, currentSpeaker);
58
+
59
+ return (
60
+ <View style={styles.container}>
61
+ {statusText ? (
62
+ <Text style={[styles.statusText, { color: colors.text }]}>
63
+ {statusText}
64
+ </Text>
65
+ ) : null}
66
+
67
+ {roundComplete && (
68
+ <Pressable
69
+ onPress={onContinueCohost}
70
+ style={styles.continueButton}
71
+ >
72
+ <Text style={styles.continueText}>Continue conversation</Text>
73
+ </Pressable>
74
+ )}
75
+
76
+ {roomState === 'connected' && (
77
+ <View style={styles.buttonRow}>
78
+ <View style={styles.buttonWrapper}>
79
+ <Pressable
80
+ onPress={onToggleMute}
81
+ style={[
82
+ styles.button,
83
+ { backgroundColor: isMuted ? '#ef4444' : 'rgba(255,255,255,0.15)' },
84
+ ]}
85
+ >
86
+ <Text style={styles.buttonIcon}>
87
+ {isMuted ? '\u{1F507}' : '\u{1F3A4}'}
88
+ </Text>
89
+ </Pressable>
90
+ <Text style={styles.buttonLabel}>
91
+ {isMuted ? 'Unmute' : 'Mute'}
92
+ </Text>
93
+ </View>
94
+
95
+ <View style={styles.buttonWrapper}>
96
+ <Pressable
97
+ onPress={cohostActive ? onDisableCohost : onEnableCohost}
98
+ style={[
99
+ styles.button,
100
+ { backgroundColor: cohostActive ? '#8b5cf6' : 'rgba(255,255,255,0.15)' },
101
+ ]}
102
+ >
103
+ <Text style={styles.buttonIcon}>{'\u{1F465}'}</Text>
104
+ </Pressable>
105
+ <Text style={styles.buttonLabel}>
106
+ {cohostActive ? 'Solo' : 'Cohost'}
107
+ </Text>
108
+ </View>
109
+
110
+ <View style={styles.buttonWrapper}>
111
+ <Pressable
112
+ onPress={onEnd}
113
+ style={[styles.button, { backgroundColor: '#ef4444' }]}
114
+ >
115
+ <Text style={styles.endIcon}>{'\u2715'}</Text>
116
+ </Pressable>
117
+ <Text style={styles.buttonLabel}>End</Text>
118
+ </View>
119
+ </View>
120
+ )}
121
+
122
+ {roomState === 'connecting' && (
123
+ <ActivityIndicator size="large" color="#38bdf8" />
124
+ )}
125
+ </View>
126
+ );
127
+ }
128
+
129
+ const styles = StyleSheet.create({
130
+ container: {
131
+ alignItems: 'center',
132
+ paddingBottom: 24,
133
+ paddingTop: 16,
134
+ },
135
+ statusText: {
136
+ fontSize: 18,
137
+ fontWeight: '500',
138
+ marginBottom: 16,
139
+ },
140
+ buttonRow: {
141
+ flexDirection: 'row',
142
+ alignItems: 'center',
143
+ gap: 32,
144
+ },
145
+ buttonWrapper: {
146
+ alignItems: 'center',
147
+ gap: 8,
148
+ },
149
+ button: {
150
+ width: 56,
151
+ height: 56,
152
+ borderRadius: 28,
153
+ alignItems: 'center',
154
+ justifyContent: 'center',
155
+ },
156
+ buttonIcon: {
157
+ fontSize: 24,
158
+ },
159
+ endIcon: {
160
+ fontSize: 24,
161
+ color: '#ffffff',
162
+ fontWeight: '700',
163
+ },
164
+ buttonLabel: {
165
+ color: '#8E8E93',
166
+ fontSize: 12,
167
+ },
168
+ continueButton: {
169
+ marginBottom: 16,
170
+ paddingHorizontal: 20,
171
+ paddingVertical: 8,
172
+ borderRadius: 24,
173
+ backgroundColor: 'rgba(139, 92, 246, 0.3)',
174
+ },
175
+ continueText: {
176
+ color: '#a78bfa',
177
+ fontSize: 14,
178
+ fontWeight: '500',
179
+ },
180
+ });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Subtle voice mode overlay that renders the AudioWaveVisualizer
3
+ * behind the conversation message list. Preserves the normal chat theme
4
+ * with waves at reduced opacity for a seamless visual experience.
5
+ */
6
+
7
+ import { View } from 'react-native';
8
+ import Animated, { FadeIn, FadeOut } from 'react-native-reanimated';
9
+ import { AudioWaveVisualizer } from './AudioWaveVisualizer';
10
+ import type { SharedValue } from 'react-native-reanimated';
11
+ import type { AgentState } from '../../types';
12
+
13
+ interface VoiceOverlayProps {
14
+ waveAmplitude: SharedValue<number>;
15
+ agentState: AgentState;
16
+ isConnected: boolean;
17
+ }
18
+
19
+ export function VoiceOverlay({ waveAmplitude, agentState, isConnected }: VoiceOverlayProps) {
20
+ return (
21
+ <Animated.View
22
+ entering={FadeIn.duration(400)}
23
+ exiting={FadeOut.duration(300)}
24
+ style={{
25
+ position: 'absolute',
26
+ bottom: 0,
27
+ left: 0,
28
+ right: 0,
29
+ height: 350,
30
+ zIndex: 5,
31
+ pointerEvents: 'none',
32
+ }}
33
+ >
34
+ <View style={{ opacity: 0.35 }}>
35
+ <AudioWaveVisualizer
36
+ waveAmplitude={waveAmplitude}
37
+ agentState={agentState}
38
+ isConnected={isConnected}
39
+ />
40
+ </View>
41
+ </Animated.View>
42
+ );
43
+ }