@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alia.onl/sdk",
3
- "version": "1.0.0",
4
- "description": "Alia AI chat SDK — reusable bottom sheet chat component for Oxy apps",
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.background === '#000000';
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, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated';
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: Animated.SharedValue<number>;
31
+ scrollOffsetY: SharedValue<number>;
17
32
  }
18
33
 
19
- function ToolStatus({ invocation }: { invocation: ToolInvocation }) {
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 label =
30
- invocation.state === 'call'
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
- {invocation.state === 'call' && (
37
- <View style={[styles.toolDot, { backgroundColor: colors.tint }]} />
38
- )}
39
- {invocation.state === 'result' && (
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.background === '#000000';
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 style={styles.assistantMessage}>
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
- <ToolStatus key={`${tool.toolName}-${i}`} invocation={tool} />
131
+ <ToolBullet key={`${tool.toolCallId || tool.toolName}-${i}`} invocation={tool} />
81
132
  ))}
82
133
 
83
- {/* Text content */}
84
- {(message.content || isStreamingThis) && (
85
- <Text style={[styles.assistantText, { color: colors.text }]}>
86
- {message.content || (isStreamingThis ? '\u2758' : '')}
87
- </Text>
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
- </View>
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
- assistantText: {
239
+ cursor: {
183
240
  fontSize: 15,
184
- lineHeight: 24,
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.background === '#000000';
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
- <View style={[styles.aliaAvatar, { backgroundColor: colors.tint }]}>
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.background === '#000000';
13
+ const isDark = colors.isDark;
14
14
 
15
15
  if (suggestions.length === 0) return null;
16
16