@alia.onl/sdk 10.1.1 → 11.1.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.
- package/CHANGELOG.md +67 -0
- package/README.md +24 -0
- package/package.json +4 -6
- package/src/components/AliaAvatar.tsx +66 -0
- package/src/components/AliaChatContent.tsx +236 -112
- package/src/components/AliaChatHeader.tsx +118 -0
- package/src/components/AliaChatMessageList.tsx +197 -260
- package/src/components/AliaChatScreen.tsx +23 -24
- package/src/components/AliaChatSheet.tsx +70 -316
- package/src/components/AliaWelcomeMessage.tsx +47 -50
- package/src/components/AmbientField.tsx +577 -0
- package/src/components/IdentityMark.tsx +10 -2
- package/src/components/Markdown.tsx +116 -40
- package/src/components/PlanPreviewCard.tsx +71 -55
- package/src/components/QuestionCard.tsx +62 -0
- package/src/components/ThinkingBlock.tsx +47 -0
- package/src/components/VoiceModeIcon.tsx +10 -0
- package/src/components/voice/VoiceControls.tsx +22 -73
- package/src/components/voice/VoiceSession.tsx +2 -1
- package/src/hooks/useAliaChat.ts +22 -15
- package/src/hooks/useAmbientWave.ts +35 -5
- package/src/hooks/useSoundEffects.ts +14 -11
- package/src/hooks/useSpeechToText.ts +9 -2
- package/src/hooks/useTTS.ts +163 -156
- package/src/hooks/useVoiceRoom.ts +228 -170
- package/src/index.ts +26 -6
- package/src/lib/catalogue.ts +17 -4
- package/src/lib/chat-stream.ts +25 -7
- package/src/lib/chat-transport.ts +2 -1
- package/src/lib/citations.ts +63 -0
- package/src/lib/content-utils.ts +3 -2
- package/src/lib/image-data-url.ts +15 -0
- package/src/lib/keyboard.tsx +1 -1
- package/src/lib/labels.tsx +81 -0
- package/src/lib/questions.ts +78 -0
- package/src/lib/speech-recognition.native.ts +15 -4
- package/src/lib/speech-recognition.ts +27 -7
- package/src/lib/task-labels.ts +67 -0
- package/src/lib/tool-call-status.ts +28 -0
- package/src/lib/tool-registry.ts +14 -14
- package/src/lib/voice-text.ts +12 -6
- package/src/lib/voice-turn.ts +4 -1
- package/src/lib/work-log-copy.ts +61 -0
- package/src/lib/work-log.ts +378 -0
- package/src/tools.ts +18 -0
- package/src/types.ts +5 -2
- package/src/voice.ts +12 -4
- package/src/components/Reasoning.tsx +0 -152
- package/src/components/ResearchProgressCard.tsx +0 -159
- package/src/components/ThinkingIndicator.tsx +0 -98
- package/src/components/ui/button.tsx +0 -104
- package/src/components/ui/chat-text-input.tsx +0 -184
- package/src/components/ui/dropdown-menu.tsx +0 -2
- package/src/components/ui/dropdown-menu.web.tsx +0 -383
- package/src/components/ui/prompt-input/actions.tsx +0 -28
- package/src/components/ui/prompt-input/add-menu.tsx +0 -83
- package/src/components/ui/prompt-input/attachments.tsx +0 -181
- package/src/components/ui/prompt-input/autocomplete.tsx +0 -168
- package/src/components/ui/prompt-input/context.tsx +0 -71
- package/src/components/ui/prompt-input/mic-button.tsx +0 -53
- package/src/components/ui/prompt-input/prompt-input.tsx +0 -279
- package/src/components/ui/prompt-input/submit-button.tsx +0 -50
- package/src/components/ui/prompt-input/textarea.tsx +0 -80
- package/src/components/ui/text.tsx +0 -29
- package/src/components/voice/AudioWaveVisualizer.tsx +0 -444
- package/src/components/voice/VoiceOverlay.tsx +0 -75
|
@@ -1,29 +0,0 @@
|
|
|
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 };
|
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useMemo } from 'react';
|
|
2
|
-
import { View, Platform, useWindowDimensions, useColorScheme } 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
|
-
/** Cross-platform blur: CSS string on web, RN array syntax on native */
|
|
19
|
-
type BlurStyle =
|
|
20
|
-
| { filter: string; WebkitFilter: string }
|
|
21
|
-
| { filter: Array<{ blur: number }> };
|
|
22
|
-
|
|
23
|
-
function blurStyle(radius: number): BlurStyle {
|
|
24
|
-
return Platform.OS === 'web'
|
|
25
|
-
? { filter: `blur(${radius}px)`, WebkitFilter: `blur(${radius}px)` }
|
|
26
|
-
: { filter: [{ blur: radius }] };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const GLOW_HEIGHT = 350;
|
|
30
|
-
const NUM_POINTS = 8;
|
|
31
|
-
|
|
32
|
-
// Ocean-style wave layers: fill from bottom to a surface level, crests undulate
|
|
33
|
-
interface WaveLayerConfig {
|
|
34
|
-
color: string;
|
|
35
|
-
surfaceY: number; // surface level as % from top (50 = middle)
|
|
36
|
-
waveHeight: number; // base crest height in px
|
|
37
|
-
maxWaveBoost: number; // extra crest height from audio
|
|
38
|
-
phaseOffset: number;
|
|
39
|
-
speed: number; // ms for one full horizontal cycle
|
|
40
|
-
blur: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const WAVE_LAYERS: WaveLayerConfig[] = [
|
|
44
|
-
// Deepest layer - dark blue, highest surface (closest to top)
|
|
45
|
-
{
|
|
46
|
-
color: 'rgba(12, 74, 110, 0.8)',
|
|
47
|
-
surfaceY: 35,
|
|
48
|
-
waveHeight: 14,
|
|
49
|
-
maxWaveBoost: 120,
|
|
50
|
-
phaseOffset: 0,
|
|
51
|
-
speed: 7000,
|
|
52
|
-
blur: 30,
|
|
53
|
-
},
|
|
54
|
-
// Mid layer - ocean blue
|
|
55
|
-
{
|
|
56
|
-
color: 'rgba(14, 165, 233, 0.45)',
|
|
57
|
-
surfaceY: 42,
|
|
58
|
-
waveHeight: 16,
|
|
59
|
-
maxWaveBoost: 140,
|
|
60
|
-
phaseOffset: Math.PI * 0.8,
|
|
61
|
-
speed: 5500,
|
|
62
|
-
blur: 22,
|
|
63
|
-
},
|
|
64
|
-
// Front layer - bright cyan, lowest surface
|
|
65
|
-
{
|
|
66
|
-
color: 'rgba(56, 189, 248, 0.35)',
|
|
67
|
-
surfaceY: 50,
|
|
68
|
-
waveHeight: 20,
|
|
69
|
-
maxWaveBoost: 150,
|
|
70
|
-
phaseOffset: Math.PI * 1.5,
|
|
71
|
-
speed: 4500,
|
|
72
|
-
blur: 16,
|
|
73
|
-
},
|
|
74
|
-
// Accent layer - indigo/purple
|
|
75
|
-
{
|
|
76
|
-
color: 'rgba(99, 102, 241, 0.25)',
|
|
77
|
-
surfaceY: 38,
|
|
78
|
-
waveHeight: 12,
|
|
79
|
-
maxWaveBoost: 100,
|
|
80
|
-
phaseOffset: Math.PI * 0.3,
|
|
81
|
-
speed: 8000,
|
|
82
|
-
blur: 28,
|
|
83
|
-
},
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
// Background glow blobs for ambient color
|
|
87
|
-
interface BlobConfig {
|
|
88
|
-
color: string;
|
|
89
|
-
baseWidth: number;
|
|
90
|
-
baseHeight: number;
|
|
91
|
-
x: number;
|
|
92
|
-
y: number;
|
|
93
|
-
blur: number;
|
|
94
|
-
amplitudeScale: number;
|
|
95
|
-
phaseSpeed: number;
|
|
96
|
-
phaseDirection: 1 | -1;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const BLOBS: BlobConfig[] = [
|
|
100
|
-
{
|
|
101
|
-
color: '#0c4a6e',
|
|
102
|
-
baseWidth: 320,
|
|
103
|
-
baseHeight: 200,
|
|
104
|
-
x: 50,
|
|
105
|
-
y: 55,
|
|
106
|
-
blur: 80,
|
|
107
|
-
amplitudeScale: 1.0,
|
|
108
|
-
phaseSpeed: 4000,
|
|
109
|
-
phaseDirection: 1,
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
color: '#0ea5e9',
|
|
113
|
-
baseWidth: 220,
|
|
114
|
-
baseHeight: 140,
|
|
115
|
-
x: 38,
|
|
116
|
-
y: 65,
|
|
117
|
-
blur: 60,
|
|
118
|
-
amplitudeScale: 1.2,
|
|
119
|
-
phaseSpeed: 3500,
|
|
120
|
-
phaseDirection: -1,
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
color: '#6366f1',
|
|
124
|
-
baseWidth: 180,
|
|
125
|
-
baseHeight: 160,
|
|
126
|
-
x: 70,
|
|
127
|
-
y: 55,
|
|
128
|
-
blur: 70,
|
|
129
|
-
amplitudeScale: 0.7,
|
|
130
|
-
phaseSpeed: 4500,
|
|
131
|
-
phaseDirection: -1,
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
color: '#a855f7',
|
|
135
|
-
baseWidth: 130,
|
|
136
|
-
baseHeight: 110,
|
|
137
|
-
x: 78,
|
|
138
|
-
y: 65,
|
|
139
|
-
blur: 60,
|
|
140
|
-
amplitudeScale: 0.5,
|
|
141
|
-
phaseSpeed: 5500,
|
|
142
|
-
phaseDirection: 1,
|
|
143
|
-
},
|
|
144
|
-
];
|
|
145
|
-
|
|
146
|
-
// Colors per agent state for waves — speaking blends blue + purple
|
|
147
|
-
const WAVE_COLORS: Record<AgentState, WaveLayerConfig['color'][]> = {
|
|
148
|
-
idle: [
|
|
149
|
-
'rgba(12, 74, 110, 0.8)',
|
|
150
|
-
'rgba(14, 165, 233, 0.45)',
|
|
151
|
-
'rgba(56, 189, 248, 0.35)',
|
|
152
|
-
'rgba(99, 102, 241, 0.25)',
|
|
153
|
-
],
|
|
154
|
-
listening: [
|
|
155
|
-
'rgba(12, 74, 110, 0.8)',
|
|
156
|
-
'rgba(14, 165, 233, 0.45)',
|
|
157
|
-
'rgba(56, 189, 248, 0.35)',
|
|
158
|
-
'rgba(99, 102, 241, 0.25)',
|
|
159
|
-
],
|
|
160
|
-
thinking: [
|
|
161
|
-
'rgba(120, 80, 20, 0.75)',
|
|
162
|
-
'rgba(234, 179, 8, 0.45)',
|
|
163
|
-
'rgba(251, 191, 36, 0.35)',
|
|
164
|
-
'rgba(253, 224, 71, 0.25)',
|
|
165
|
-
],
|
|
166
|
-
speaking: [
|
|
167
|
-
'rgba(30, 40, 100, 0.8)',
|
|
168
|
-
'rgba(99, 102, 241, 0.5)',
|
|
169
|
-
'rgba(147, 51, 234, 0.4)',
|
|
170
|
-
'rgba(56, 189, 248, 0.3)',
|
|
171
|
-
],
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
const BLOB_COLORS: Record<AgentState, string[]> = {
|
|
175
|
-
idle: ['#0c4a6e', '#0ea5e9', '#6366f1', '#a855f7'],
|
|
176
|
-
listening: ['#0c4a6e', '#0ea5e9', '#6366f1', '#a855f7'],
|
|
177
|
-
thinking: ['#92400e', '#eab308', '#f59e0b', '#fbbf24'],
|
|
178
|
-
speaking: ['#1e2870', '#6366f1', '#9333ea', '#0ea5e9'],
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Derive wave & blob palettes from a hex color
|
|
182
|
-
/** Parse a CSS color string (#rgb, #rrggbb, rgb(...), rgba(...)) to [r,g,b]; null if unrecognized. */
|
|
183
|
-
function parseColor(input: string): [number, number, number] | null {
|
|
184
|
-
const s = input.trim();
|
|
185
|
-
if (s.startsWith('#')) {
|
|
186
|
-
const h = s.slice(1);
|
|
187
|
-
const expanded = h.length === 3 ? h[0] + h[0] + h[1] + h[1] + h[2] + h[2] : h;
|
|
188
|
-
if (expanded.length !== 6) return null;
|
|
189
|
-
const r = parseInt(expanded.slice(0, 2), 16);
|
|
190
|
-
const g = parseInt(expanded.slice(2, 4), 16);
|
|
191
|
-
const b = parseInt(expanded.slice(4, 6), 16);
|
|
192
|
-
return Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b) ? null : [r, g, b];
|
|
193
|
-
}
|
|
194
|
-
// rgb()/rgba() — comma OR space separated (rgb(r, g, b) and modern rgb(r g b / a)); alpha ignored
|
|
195
|
-
const match = s.match(/^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/i);
|
|
196
|
-
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function darken(rgb: [number, number, number], factor: number): [number, number, number] {
|
|
200
|
-
return [Math.round(rgb[0] * factor), Math.round(rgb[1] * factor), Math.round(rgb[2] * factor)];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function lighten(rgb: [number, number, number], factor: number): [number, number, number] {
|
|
204
|
-
return [
|
|
205
|
-
Math.min(255, Math.round(rgb[0] + (255 - rgb[0]) * factor)),
|
|
206
|
-
Math.min(255, Math.round(rgb[1] + (255 - rgb[1]) * factor)),
|
|
207
|
-
Math.min(255, Math.round(rgb[2] + (255 - rgb[2]) * factor)),
|
|
208
|
-
];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function deriveWavePalette(color: string, isDark: boolean): { waves: string[]; blobs: string[] } | null {
|
|
212
|
-
const rgb = parseColor(color);
|
|
213
|
-
if (!rgb) return null;
|
|
214
|
-
// Dark mode: lighter/brighter waves visible against dark bg
|
|
215
|
-
// Light mode: darker waves visible against light bg
|
|
216
|
-
const layer0 = isDark ? darken(rgb, 0.55) : darken(rgb, 0.3);
|
|
217
|
-
const layer1 = isDark ? lighten(rgb, 0.15) : darken(rgb, 0.5);
|
|
218
|
-
const layer2 = isDark ? lighten(rgb, 0.4) : darken(rgb, 0.15);
|
|
219
|
-
const layer3 = isDark ? lighten(rgb, 0.6) : rgb;
|
|
220
|
-
return {
|
|
221
|
-
waves: [
|
|
222
|
-
`rgba(${layer0[0]}, ${layer0[1]}, ${layer0[2]}, 0.8)`,
|
|
223
|
-
`rgba(${layer1[0]}, ${layer1[1]}, ${layer1[2]}, 0.45)`,
|
|
224
|
-
`rgba(${layer2[0]}, ${layer2[1]}, ${layer2[2]}, 0.35)`,
|
|
225
|
-
`rgba(${layer3[0]}, ${layer3[1]}, ${layer3[2]}, 0.25)`,
|
|
226
|
-
],
|
|
227
|
-
blobs: [
|
|
228
|
-
`rgb(${layer0[0]}, ${layer0[1]}, ${layer0[2]})`,
|
|
229
|
-
`rgb(${layer1[0]}, ${layer1[1]}, ${layer1[2]})`,
|
|
230
|
-
`rgb(${layer2[0]}, ${layer2[1]}, ${layer2[2]})`,
|
|
231
|
-
`rgb(${layer3[0]}, ${layer3[1]}, ${layer3[2]})`,
|
|
232
|
-
],
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
interface AudioWaveVisualizerProps {
|
|
237
|
-
waveAmplitude: SharedValue<number>;
|
|
238
|
-
agentState: AgentState;
|
|
239
|
-
isConnected?: boolean;
|
|
240
|
-
/** Theme primary color hex — derives idle/listening wave palette */
|
|
241
|
-
primaryColor?: string;
|
|
242
|
-
/** Whether the app is in dark mode (affects wave brightness) */
|
|
243
|
-
isDarkMode?: boolean;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export function AudioWaveVisualizer({
|
|
247
|
-
waveAmplitude,
|
|
248
|
-
agentState = 'idle',
|
|
249
|
-
isConnected = true,
|
|
250
|
-
primaryColor,
|
|
251
|
-
isDarkMode,
|
|
252
|
-
}: AudioWaveVisualizerProps) {
|
|
253
|
-
const { width: screenWidth } = useWindowDimensions();
|
|
254
|
-
const scheme = useColorScheme();
|
|
255
|
-
const effectiveIsDark = isDarkMode ?? (scheme === 'dark');
|
|
256
|
-
const state = WAVE_COLORS[agentState] ? agentState : 'idle';
|
|
257
|
-
|
|
258
|
-
// Use theme-derived palette for idle/listening, keep hardcoded for thinking/speaking
|
|
259
|
-
const themed = useMemo(
|
|
260
|
-
() => (primaryColor ? deriveWavePalette(primaryColor, effectiveIsDark) : null),
|
|
261
|
-
[primaryColor, effectiveIsDark],
|
|
262
|
-
);
|
|
263
|
-
const isThemed = (state === 'idle' || state === 'listening') && themed != null;
|
|
264
|
-
const waveColors = isThemed ? themed.waves : WAVE_COLORS[state];
|
|
265
|
-
const blobColors = isThemed ? themed.blobs : BLOB_COLORS[state];
|
|
266
|
-
|
|
267
|
-
return (
|
|
268
|
-
<View
|
|
269
|
-
style={{
|
|
270
|
-
width: screenWidth,
|
|
271
|
-
height: GLOW_HEIGHT,
|
|
272
|
-
position: 'relative',
|
|
273
|
-
}}
|
|
274
|
-
>
|
|
275
|
-
{/* Background glow blobs */}
|
|
276
|
-
{BLOBS.map((blob, i) => (
|
|
277
|
-
<GlowBlob
|
|
278
|
-
key={`blob-${i}`}
|
|
279
|
-
blob={blob}
|
|
280
|
-
color={blobColors[i]}
|
|
281
|
-
waveAmplitude={waveAmplitude}
|
|
282
|
-
isConnected={isConnected}
|
|
283
|
-
screenWidth={screenWidth}
|
|
284
|
-
/>
|
|
285
|
-
))}
|
|
286
|
-
|
|
287
|
-
{/* Ocean wave layers — filled from surface down to bottom, with blur */}
|
|
288
|
-
{WAVE_LAYERS.map((layer, i) => (
|
|
289
|
-
<OceanWave
|
|
290
|
-
key={`wave-${i}`}
|
|
291
|
-
layer={layer}
|
|
292
|
-
color={waveColors[i]}
|
|
293
|
-
waveAmplitude={waveAmplitude}
|
|
294
|
-
isConnected={isConnected}
|
|
295
|
-
screenWidth={screenWidth}
|
|
296
|
-
/>
|
|
297
|
-
))}
|
|
298
|
-
</View>
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Ocean wave: surface undulates horizontally, filled from surface to bottom
|
|
303
|
-
function OceanWave({
|
|
304
|
-
layer,
|
|
305
|
-
color,
|
|
306
|
-
waveAmplitude,
|
|
307
|
-
isConnected,
|
|
308
|
-
screenWidth,
|
|
309
|
-
}: {
|
|
310
|
-
layer: WaveLayerConfig;
|
|
311
|
-
color: string;
|
|
312
|
-
waveAmplitude: SharedValue<number>;
|
|
313
|
-
isConnected: boolean;
|
|
314
|
-
screenWidth: number;
|
|
315
|
-
}) {
|
|
316
|
-
const phase = useSharedValue(0);
|
|
317
|
-
|
|
318
|
-
useEffect(() => {
|
|
319
|
-
phase.value = 0;
|
|
320
|
-
phase.value = withRepeat(
|
|
321
|
-
withTiming(Math.PI * 2, { duration: layer.speed, easing: Easing.linear }),
|
|
322
|
-
-1,
|
|
323
|
-
false,
|
|
324
|
-
);
|
|
325
|
-
}, []);
|
|
326
|
-
|
|
327
|
-
const animatedProps = useAnimatedProps(() => {
|
|
328
|
-
const amp = waveAmplitude.value;
|
|
329
|
-
// Surface Y position (fixed, doesn't move with audio)
|
|
330
|
-
const surfaceY = (layer.surfaceY / 100) * GLOW_HEIGHT;
|
|
331
|
-
// Wave crest height: base + audio boost
|
|
332
|
-
const crestHeight = layer.waveHeight + amp * layer.maxWaveBoost;
|
|
333
|
-
const currentPhase = phase.value + layer.phaseOffset;
|
|
334
|
-
const segW = screenWidth / NUM_POINTS;
|
|
335
|
-
|
|
336
|
-
// Start at bottom-left corner
|
|
337
|
-
let d = `M 0 ${GLOW_HEIGHT}`;
|
|
338
|
-
|
|
339
|
-
// Up to first surface point
|
|
340
|
-
const firstY = surfaceY + Math.sin(currentPhase) * crestHeight;
|
|
341
|
-
d += ` L 0 ${firstY}`;
|
|
342
|
-
|
|
343
|
-
// Draw undulating surface across the width
|
|
344
|
-
for (let i = 0; i < NUM_POINTS; i++) {
|
|
345
|
-
const x1 = (i + 1) * segW;
|
|
346
|
-
const y0 =
|
|
347
|
-
surfaceY +
|
|
348
|
-
Math.sin(currentPhase + (i / NUM_POINTS) * Math.PI * 2) * crestHeight;
|
|
349
|
-
const y1 =
|
|
350
|
-
surfaceY +
|
|
351
|
-
Math.sin(currentPhase + ((i + 1) / NUM_POINTS) * Math.PI * 2) * crestHeight;
|
|
352
|
-
|
|
353
|
-
const cp1x = i * segW + segW * 0.5;
|
|
354
|
-
const cp2x = x1 - segW * 0.5;
|
|
355
|
-
|
|
356
|
-
d += ` C ${cp1x} ${y0}, ${cp2x} ${y1}, ${x1} ${y1}`;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Close: down to bottom-right, across to bottom-left
|
|
360
|
-
d += ` L ${screenWidth} ${GLOW_HEIGHT} Z`;
|
|
361
|
-
|
|
362
|
-
return { d };
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
return (
|
|
366
|
-
<View
|
|
367
|
-
style={{
|
|
368
|
-
position: 'absolute',
|
|
369
|
-
top: 0,
|
|
370
|
-
left: 0,
|
|
371
|
-
right: 0,
|
|
372
|
-
bottom: 0,
|
|
373
|
-
opacity: isConnected ? 1 : 0,
|
|
374
|
-
...blurStyle(layer.blur),
|
|
375
|
-
pointerEvents: 'none',
|
|
376
|
-
}}
|
|
377
|
-
>
|
|
378
|
-
<Svg width={screenWidth} height={GLOW_HEIGHT}>
|
|
379
|
-
<AnimatedPath animatedProps={animatedProps} fill={color} />
|
|
380
|
-
</Svg>
|
|
381
|
-
</View>
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Ambient glow blob
|
|
386
|
-
function GlowBlob({
|
|
387
|
-
blob,
|
|
388
|
-
color,
|
|
389
|
-
waveAmplitude,
|
|
390
|
-
isConnected,
|
|
391
|
-
screenWidth,
|
|
392
|
-
}: {
|
|
393
|
-
blob: BlobConfig;
|
|
394
|
-
color: string;
|
|
395
|
-
waveAmplitude: SharedValue<number>;
|
|
396
|
-
isConnected: boolean;
|
|
397
|
-
screenWidth: number;
|
|
398
|
-
}) {
|
|
399
|
-
const drift = useSharedValue(0);
|
|
400
|
-
|
|
401
|
-
useEffect(() => {
|
|
402
|
-
drift.value = 0;
|
|
403
|
-
drift.value = withRepeat(
|
|
404
|
-
withSequence(
|
|
405
|
-
withTiming(1, {
|
|
406
|
-
duration: blob.phaseSpeed,
|
|
407
|
-
easing: Easing.bezier(0.37, 0, 0.63, 1),
|
|
408
|
-
}),
|
|
409
|
-
withTiming(0, {
|
|
410
|
-
duration: blob.phaseSpeed,
|
|
411
|
-
easing: Easing.bezier(0.37, 0, 0.63, 1),
|
|
412
|
-
}),
|
|
413
|
-
),
|
|
414
|
-
-1,
|
|
415
|
-
false,
|
|
416
|
-
);
|
|
417
|
-
}, []);
|
|
418
|
-
|
|
419
|
-
const animatedStyle = useAnimatedStyle(() => {
|
|
420
|
-
const amp = waveAmplitude.value;
|
|
421
|
-
const scale = 1 + amp * 0.3 * blob.amplitudeScale;
|
|
422
|
-
const driftOffset = (drift.value - 0.5) * 20 * blob.phaseDirection;
|
|
423
|
-
const opacity = isConnected ? 0.45 + amp * 0.35 * blob.amplitudeScale : 0;
|
|
424
|
-
|
|
425
|
-
const w = blob.baseWidth * scale;
|
|
426
|
-
const h = blob.baseHeight * scale;
|
|
427
|
-
const left = (blob.x / 100) * screenWidth - w / 2 + driftOffset;
|
|
428
|
-
const top = (blob.y / 100) * GLOW_HEIGHT - h / 2;
|
|
429
|
-
|
|
430
|
-
return {
|
|
431
|
-
position: 'absolute' as const,
|
|
432
|
-
width: w,
|
|
433
|
-
height: h,
|
|
434
|
-
left,
|
|
435
|
-
top,
|
|
436
|
-
borderRadius: w / 2,
|
|
437
|
-
backgroundColor: color,
|
|
438
|
-
opacity,
|
|
439
|
-
transform: [{ scale }],
|
|
440
|
-
};
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
return <Animated.View style={[animatedStyle, blurStyle(blob.blur)]} />;
|
|
444
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Subtle wave overlay that renders the AudioWaveVisualizer behind the
|
|
3
|
-
* conversation message list. Always mounted; `intensity` ramps the opacity
|
|
4
|
-
* between a barely-there idle ambient and a prominent speaking state.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import Animated, {
|
|
8
|
-
FadeIn,
|
|
9
|
-
FadeOut,
|
|
10
|
-
useSharedValue,
|
|
11
|
-
useAnimatedReaction,
|
|
12
|
-
useAnimatedStyle,
|
|
13
|
-
withTiming,
|
|
14
|
-
} from 'react-native-reanimated';
|
|
15
|
-
import { AudioWaveVisualizer } from './AudioWaveVisualizer';
|
|
16
|
-
import type { SharedValue } from 'react-native-reanimated';
|
|
17
|
-
import type { AgentState } from '../../types';
|
|
18
|
-
|
|
19
|
-
interface VoiceOverlayProps {
|
|
20
|
-
waveAmplitude: SharedValue<number>;
|
|
21
|
-
agentState: AgentState;
|
|
22
|
-
/** Overlay opacity target (default 0.35); ramps over 500ms when it changes. */
|
|
23
|
-
intensity?: number;
|
|
24
|
-
isConnected?: boolean;
|
|
25
|
-
primaryColor?: string;
|
|
26
|
-
isDarkMode?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function VoiceOverlay({
|
|
30
|
-
waveAmplitude,
|
|
31
|
-
agentState,
|
|
32
|
-
intensity = 0.35,
|
|
33
|
-
isConnected = true,
|
|
34
|
-
primaryColor,
|
|
35
|
-
isDarkMode,
|
|
36
|
-
}: VoiceOverlayProps) {
|
|
37
|
-
// Imperative opacity ramp (web-safe): mapper-started style animations don't
|
|
38
|
-
// tick on reanimated-web, so drive a shared value via useAnimatedReaction.
|
|
39
|
-
const opacity = useSharedValue(intensity);
|
|
40
|
-
useAnimatedReaction(
|
|
41
|
-
() => intensity,
|
|
42
|
-
(target, previous) => {
|
|
43
|
-
if (target === previous) return;
|
|
44
|
-
opacity.value = withTiming(target, { duration: 500 });
|
|
45
|
-
},
|
|
46
|
-
[intensity],
|
|
47
|
-
);
|
|
48
|
-
const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<Animated.View
|
|
52
|
-
entering={FadeIn.duration(400)}
|
|
53
|
-
exiting={FadeOut.duration(300)}
|
|
54
|
-
style={{
|
|
55
|
-
position: 'absolute',
|
|
56
|
-
bottom: 0,
|
|
57
|
-
left: 0,
|
|
58
|
-
right: 0,
|
|
59
|
-
height: 350,
|
|
60
|
-
zIndex: 5,
|
|
61
|
-
pointerEvents: 'none',
|
|
62
|
-
}}
|
|
63
|
-
>
|
|
64
|
-
<Animated.View style={animatedStyle}>
|
|
65
|
-
<AudioWaveVisualizer
|
|
66
|
-
waveAmplitude={waveAmplitude}
|
|
67
|
-
agentState={agentState}
|
|
68
|
-
isConnected={isConnected}
|
|
69
|
-
primaryColor={primaryColor}
|
|
70
|
-
isDarkMode={isDarkMode}
|
|
71
|
-
/>
|
|
72
|
-
</Animated.View>
|
|
73
|
-
</Animated.View>
|
|
74
|
-
);
|
|
75
|
-
}
|