@alia.onl/sdk 1.0.0 → 2.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.
- package/package.json +15 -3
- package/src/components/AliaChatInput.tsx +1 -1
- package/src/components/AliaChatMessageList.tsx +85 -32
- package/src/components/AliaChatSheet.tsx +14 -18
- package/src/components/AliaChatSuggestions.tsx +1 -1
- package/src/components/AliaFace.tsx +402 -0
- package/src/components/Markdown.tsx +288 -0
- package/src/components/Reasoning.tsx +154 -0
- package/src/components/ThinkingIndicator.tsx +134 -0
- package/src/components/voice/AudioWaveVisualizer.tsx +377 -0
- package/src/components/voice/VoiceControls.tsx +180 -0
- package/src/components/voice/VoiceOverlay.tsx +43 -0
- package/src/hooks/useAliaChat.ts +265 -87
- package/src/hooks/useAudioLevelMonitor.ts +142 -0
- package/src/hooks/useAudioLevels.ts +51 -0
- package/src/hooks/useSoundEffects.ts +128 -0
- package/src/hooks/useSpeechToText.ts +180 -0
- package/src/hooks/useTTS.ts +233 -0
- package/src/hooks/useVoiceRoom.ts +420 -0
- package/src/index.ts +40 -6
- package/src/optional-deps.d.ts +69 -0
- package/src/theme.ts +25 -2
- package/src/types.ts +59 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alia.onl/sdk",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Alia AI chat SDK — reusable
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Alia AI chat SDK — reusable chat UI, voice, and streaming components for Oxy apps",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
7
7
|
"source": "src/index.ts",
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"react-native-markdown-display": "^7.0.2-0",
|
|
15
|
+
"zustand": ">=4"
|
|
16
|
+
},
|
|
13
17
|
"peerDependencies": {
|
|
14
18
|
"react": ">=18",
|
|
15
19
|
"react-native": ">=0.73",
|
|
@@ -18,7 +22,15 @@
|
|
|
18
22
|
"react-native-reanimated": ">=3",
|
|
19
23
|
"react-native-gesture-handler": ">=2",
|
|
20
24
|
"react-native-safe-area-context": ">=4",
|
|
21
|
-
"react-native-keyboard-controller": ">=1"
|
|
25
|
+
"react-native-keyboard-controller": ">=1",
|
|
26
|
+
"react-native-svg": ">=13",
|
|
27
|
+
"livekit-client": ">=2.0.0",
|
|
28
|
+
"expo-audio": ">=0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"livekit-client": { "optional": true },
|
|
32
|
+
"expo-audio": { "optional": true },
|
|
33
|
+
"react-native-svg": { "optional": true }
|
|
22
34
|
},
|
|
23
35
|
"devDependencies": {
|
|
24
36
|
"typescript": "^5.9.3"
|
|
@@ -19,7 +19,7 @@ export function AliaChatInput({ onSend, isStreaming }: AliaChatInputProps) {
|
|
|
19
19
|
const [input, setInput] = useState('');
|
|
20
20
|
const colors = useAliaColors();
|
|
21
21
|
const insets = useSafeAreaInsets();
|
|
22
|
-
const isDark = colors.
|
|
22
|
+
const isDark = colors.isDark;
|
|
23
23
|
|
|
24
24
|
const canSend = input.trim().length > 0 && !isStreaming;
|
|
25
25
|
|
|
@@ -3,20 +3,37 @@ import {
|
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
5
5
|
StyleSheet,
|
|
6
|
-
Platform,
|
|
7
6
|
} from 'react-native';
|
|
8
7
|
import { BlurView } from 'expo-blur';
|
|
9
|
-
import Animated, {
|
|
8
|
+
import Animated, {
|
|
9
|
+
useAnimatedScrollHandler,
|
|
10
|
+
useSharedValue,
|
|
11
|
+
useAnimatedStyle,
|
|
12
|
+
withRepeat,
|
|
13
|
+
withTiming,
|
|
14
|
+
withSequence,
|
|
15
|
+
cancelAnimation,
|
|
16
|
+
Easing,
|
|
17
|
+
FadeInUp,
|
|
18
|
+
type SharedValue,
|
|
19
|
+
} from 'react-native-reanimated';
|
|
10
20
|
import { useAliaColors } from '../theme';
|
|
21
|
+
import { AliaMarkdown } from './Markdown';
|
|
22
|
+
import { ThinkingIndicator } from './ThinkingIndicator';
|
|
23
|
+
import { Reasoning, ReasoningTrigger, ReasoningContent } from './Reasoning';
|
|
11
24
|
import type { ChatMessage, ToolInvocation } from '../types';
|
|
12
25
|
|
|
26
|
+
const ENTER_ANIMATION = FadeInUp.duration(200);
|
|
27
|
+
|
|
13
28
|
interface AliaChatMessageListProps {
|
|
14
29
|
messages: ChatMessage[];
|
|
15
30
|
isStreaming: boolean;
|
|
16
|
-
scrollOffsetY:
|
|
31
|
+
scrollOffsetY: SharedValue<number>;
|
|
17
32
|
}
|
|
18
33
|
|
|
19
|
-
|
|
34
|
+
// ── Tool Bullet ──
|
|
35
|
+
|
|
36
|
+
function ToolBullet({ invocation }: { invocation: ToolInvocation }) {
|
|
20
37
|
const colors = useAliaColors();
|
|
21
38
|
|
|
22
39
|
// Clean up tool name for display: "oxy_inbox__searchEmails" → "Searching emails"
|
|
@@ -26,27 +43,47 @@ function ToolStatus({ invocation }: { invocation: ToolInvocation }) {
|
|
|
26
43
|
.replace(/^./, (s) => s.toUpperCase())
|
|
27
44
|
.trim();
|
|
28
45
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
? `${displayName}...`
|
|
32
|
-
: displayName;
|
|
46
|
+
const isActive = invocation.state === 'call' || invocation.state === 'partial-call';
|
|
47
|
+
const label = isActive ? `${displayName}...` : displayName;
|
|
33
48
|
|
|
34
49
|
return (
|
|
35
50
|
<View style={styles.toolStatus}>
|
|
36
|
-
{
|
|
37
|
-
<
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
<Text style={[styles.toolCheckmark, { color: colors.tint }]}>{'✓'}</Text>
|
|
51
|
+
{isActive ? (
|
|
52
|
+
<PulsingDot color={colors.tint} />
|
|
53
|
+
) : (
|
|
54
|
+
<Text style={[styles.toolCheckmark, { color: colors.tint }]}>{'\u2713'}</Text>
|
|
41
55
|
)}
|
|
42
56
|
<Text style={[styles.toolLabel, { color: colors.secondaryText }]}>{label}</Text>
|
|
43
57
|
</View>
|
|
44
58
|
);
|
|
45
59
|
}
|
|
46
60
|
|
|
61
|
+
function PulsingDot({ color }: { color: string }) {
|
|
62
|
+
const opacity = useSharedValue(1);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
opacity.value = withRepeat(
|
|
66
|
+
withSequence(
|
|
67
|
+
withTiming(0.3, { duration: 600, easing: Easing.inOut(Easing.sin) }),
|
|
68
|
+
withTiming(1, { duration: 600, easing: Easing.inOut(Easing.sin) }),
|
|
69
|
+
),
|
|
70
|
+
-1,
|
|
71
|
+
);
|
|
72
|
+
return () => cancelAnimation(opacity);
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
const animStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Animated.View style={[styles.toolDot, { backgroundColor: color }, animStyle]} />
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── User Bubble ──
|
|
83
|
+
|
|
47
84
|
function UserBubble({ content }: { content: string }) {
|
|
48
85
|
const colors = useAliaColors();
|
|
49
|
-
const isDark = colors.
|
|
86
|
+
const isDark = colors.isDark;
|
|
50
87
|
|
|
51
88
|
return (
|
|
52
89
|
<View style={styles.userMessage}>
|
|
@@ -62,34 +99,56 @@ function UserBubble({ content }: { content: string }) {
|
|
|
62
99
|
);
|
|
63
100
|
}
|
|
64
101
|
|
|
102
|
+
// ── Assistant Message ──
|
|
103
|
+
|
|
65
104
|
function AssistantMessage({
|
|
66
105
|
message,
|
|
67
|
-
isFirst,
|
|
68
106
|
isStreamingThis,
|
|
69
107
|
}: {
|
|
70
108
|
message: ChatMessage;
|
|
71
|
-
isFirst: boolean;
|
|
72
109
|
isStreamingThis: boolean;
|
|
73
110
|
}) {
|
|
74
111
|
const colors = useAliaColors();
|
|
112
|
+
const hasContent = !!message.content;
|
|
113
|
+
const hasThinking = !!message.thinking;
|
|
114
|
+
const showThinkingIndicator = isStreamingThis && !hasContent && !message.toolInvocations?.length && !hasThinking;
|
|
75
115
|
|
|
76
116
|
return (
|
|
77
|
-
<View
|
|
117
|
+
<Animated.View
|
|
118
|
+
entering={ENTER_ANIMATION}
|
|
119
|
+
style={styles.assistantMessage}
|
|
120
|
+
>
|
|
121
|
+
{/* Extended thinking / reasoning */}
|
|
122
|
+
{hasThinking && (
|
|
123
|
+
<Reasoning isStreaming={isStreamingThis && !hasContent}>
|
|
124
|
+
<ReasoningTrigger />
|
|
125
|
+
<ReasoningContent>{message.thinking!}</ReasoningContent>
|
|
126
|
+
</Reasoning>
|
|
127
|
+
)}
|
|
128
|
+
|
|
78
129
|
{/* Tool invocations */}
|
|
79
130
|
{message.toolInvocations?.map((tool, i) => (
|
|
80
|
-
<
|
|
131
|
+
<ToolBullet key={`${tool.toolCallId || tool.toolName}-${i}`} invocation={tool} />
|
|
81
132
|
))}
|
|
82
133
|
|
|
83
|
-
{/*
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
134
|
+
{/* Thinking indicator (no content yet) */}
|
|
135
|
+
{showThinkingIndicator && <ThinkingIndicator />}
|
|
136
|
+
|
|
137
|
+
{/* Text content — rendered as markdown */}
|
|
138
|
+
{hasContent && (
|
|
139
|
+
<AliaMarkdown content={message.content} />
|
|
88
140
|
)}
|
|
89
|
-
|
|
141
|
+
|
|
142
|
+
{/* Streaming cursor */}
|
|
143
|
+
{isStreamingThis && hasContent && (
|
|
144
|
+
<Text style={[styles.cursor, { color: colors.secondaryText }]}>{'\u2758'}</Text>
|
|
145
|
+
)}
|
|
146
|
+
</Animated.View>
|
|
90
147
|
);
|
|
91
148
|
}
|
|
92
149
|
|
|
150
|
+
// ── List ──
|
|
151
|
+
|
|
93
152
|
export function AliaChatMessageList({
|
|
94
153
|
messages,
|
|
95
154
|
isStreaming,
|
|
@@ -130,14 +189,12 @@ export function AliaChatMessageList({
|
|
|
130
189
|
return <UserBubble key={msg.id} content={msg.content} />;
|
|
131
190
|
}
|
|
132
191
|
|
|
133
|
-
const isFirstInGroup = i === 0 || arr[i - 1]?.role === 'user';
|
|
134
192
|
const isStreamingThis = isStreaming && i === arr.length - 1;
|
|
135
193
|
|
|
136
194
|
return (
|
|
137
195
|
<AssistantMessage
|
|
138
196
|
key={msg.id}
|
|
139
197
|
message={msg}
|
|
140
|
-
isFirst={isFirstInGroup}
|
|
141
198
|
isStreamingThis={isStreamingThis}
|
|
142
199
|
/>
|
|
143
200
|
);
|
|
@@ -179,9 +236,9 @@ const styles = StyleSheet.create({
|
|
|
179
236
|
alignItems: 'flex-start',
|
|
180
237
|
marginBottom: 12,
|
|
181
238
|
},
|
|
182
|
-
|
|
239
|
+
cursor: {
|
|
183
240
|
fontSize: 15,
|
|
184
|
-
|
|
241
|
+
marginTop: -4,
|
|
185
242
|
},
|
|
186
243
|
|
|
187
244
|
// Tool status
|
|
@@ -196,10 +253,6 @@ const styles = StyleSheet.create({
|
|
|
196
253
|
width: 8,
|
|
197
254
|
height: 8,
|
|
198
255
|
borderRadius: 4,
|
|
199
|
-
...Platform.select({
|
|
200
|
-
web: {} as any,
|
|
201
|
-
default: {},
|
|
202
|
-
}),
|
|
203
256
|
},
|
|
204
257
|
toolCheckmark: {
|
|
205
258
|
fontSize: 14,
|
|
@@ -31,6 +31,7 @@ import { useAliaChat, type UseAliaChatOptions } from '../hooks/useAliaChat';
|
|
|
31
31
|
import { AliaChatMessageList } from './AliaChatMessageList';
|
|
32
32
|
import { AliaChatInput } from './AliaChatInput';
|
|
33
33
|
import { AliaChatSuggestions } from './AliaChatSuggestions';
|
|
34
|
+
import { AliaFace, type AliaExpression } from './AliaFace';
|
|
34
35
|
import { useAliaColors } from '../theme';
|
|
35
36
|
import type { AliaChatSuggestion } from '../types';
|
|
36
37
|
|
|
@@ -61,7 +62,7 @@ export interface AliaChatSheetRef {
|
|
|
61
62
|
export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
62
63
|
({ clientContext, suggestions = [], model, apiUrl }, ref) => {
|
|
63
64
|
const colors = useAliaColors();
|
|
64
|
-
const isDark = colors.
|
|
65
|
+
const isDark = colors.isDark;
|
|
65
66
|
const insets = useSafeAreaInsets();
|
|
66
67
|
|
|
67
68
|
// Chat
|
|
@@ -147,7 +148,7 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
147
148
|
// Pan gesture for swipe-to-dismiss
|
|
148
149
|
const nativeGesture = useMemo(() => Gesture.Native(), []);
|
|
149
150
|
|
|
150
|
-
const panGesture = Gesture.Pan()
|
|
151
|
+
const panGesture = useMemo(() => Gesture.Pan()
|
|
151
152
|
.simultaneousWithExternalGesture(nativeGesture)
|
|
152
153
|
.onStart(() => {
|
|
153
154
|
'worklet';
|
|
@@ -181,7 +182,7 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
181
182
|
} else {
|
|
182
183
|
translateY.value = withSpring(0, { ...SPRING_CONFIG, velocity });
|
|
183
184
|
}
|
|
184
|
-
});
|
|
185
|
+
}), [nativeGesture, finishDismiss]);
|
|
185
186
|
|
|
186
187
|
// Animated styles
|
|
187
188
|
const backdropAnimStyle = useAnimatedStyle(() => ({
|
|
@@ -199,6 +200,15 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
199
200
|
[insets.top],
|
|
200
201
|
);
|
|
201
202
|
|
|
203
|
+
// Derive AliaFace expression from chat state
|
|
204
|
+
const faceExpression = useMemo<AliaExpression>(() => {
|
|
205
|
+
if (!isStreaming) return 'Idle A';
|
|
206
|
+
const lastMsg = messages[messages.length - 1];
|
|
207
|
+
if (lastMsg?.role === 'assistant' && lastMsg.toolInvocations?.some(t => t.state === 'call')) return 'Searching A';
|
|
208
|
+
if (lastMsg?.thinking) return 'Thinking';
|
|
209
|
+
return 'Writing E';
|
|
210
|
+
}, [messages, isStreaming]);
|
|
211
|
+
|
|
202
212
|
const showSuggestions = messages.length === 0 && suggestions.length > 0;
|
|
203
213
|
|
|
204
214
|
if (!rendered) return null;
|
|
@@ -240,9 +250,7 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
240
250
|
{/* Header */}
|
|
241
251
|
<View style={styles.header}>
|
|
242
252
|
<View style={styles.headerLeft}>
|
|
243
|
-
<
|
|
244
|
-
<Text style={styles.aliaAvatarText}>A</Text>
|
|
245
|
-
</View>
|
|
253
|
+
<AliaFace size={28} expression={faceExpression} />
|
|
246
254
|
<Text style={[styles.headerTitle, { color: colors.text }]}>
|
|
247
255
|
Alia
|
|
248
256
|
</Text>
|
|
@@ -353,18 +361,6 @@ const styles = StyleSheet.create({
|
|
|
353
361
|
alignItems: 'center',
|
|
354
362
|
gap: 10,
|
|
355
363
|
},
|
|
356
|
-
aliaAvatar: {
|
|
357
|
-
width: 28,
|
|
358
|
-
height: 28,
|
|
359
|
-
borderRadius: 14,
|
|
360
|
-
alignItems: 'center',
|
|
361
|
-
justifyContent: 'center',
|
|
362
|
-
},
|
|
363
|
-
aliaAvatarText: {
|
|
364
|
-
color: '#FFFFFF',
|
|
365
|
-
fontSize: 14,
|
|
366
|
-
fontWeight: '700',
|
|
367
|
-
},
|
|
368
364
|
headerTitle: {
|
|
369
365
|
fontSize: 18,
|
|
370
366
|
fontWeight: '600',
|
|
@@ -10,7 +10,7 @@ interface AliaChatSuggestionsProps {
|
|
|
10
10
|
|
|
11
11
|
export function AliaChatSuggestions({ suggestions, onSelect }: AliaChatSuggestionsProps) {
|
|
12
12
|
const colors = useAliaColors();
|
|
13
|
-
const isDark = colors.
|
|
13
|
+
const isDark = colors.isDark;
|
|
14
14
|
|
|
15
15
|
if (suggestions.length === 0) return null;
|
|
16
16
|
|