@alia.onl/sdk 3.1.0 → 3.1.2

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@alia.onl/sdk",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
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",
@@ -11,13 +11,16 @@
11
11
  "access": "public"
12
12
  },
13
13
  "dependencies": {
14
+ "@tanstack/query-async-storage-persister": "^5.100.0",
15
+ "@tanstack/react-query": "^5.100.0",
16
+ "@tanstack/react-query-persist-client": "^5.100.0",
14
17
  "react-native-markdown-display": "^7.0.2-0",
15
18
  "zustand": ">=4"
16
19
  },
17
20
  "peerDependencies": {
18
21
  "react": ">=18",
19
22
  "react-native": ">=0.73",
20
- "@oxyhq/services": ">=6.0.0",
23
+ "@oxyhq/services": "^10.2.3",
21
24
  "expo-blur": "*",
22
25
  "react-native-reanimated": ">=3",
23
26
  "react-native-gesture-handler": ">=2",
@@ -39,20 +42,48 @@
39
42
  "@radix-ui/react-dropdown-menu": ">=2"
40
43
  },
41
44
  "peerDependenciesMeta": {
42
- "livekit-client": { "optional": true },
43
- "expo-audio": { "optional": true },
44
- "react-native-svg": { "optional": true },
45
- "nativewind": { "optional": true },
46
- "clsx": { "optional": true },
47
- "tailwind-merge": { "optional": true },
48
- "class-variance-authority": { "optional": true },
49
- "zeego": { "optional": true },
50
- "expo-image": { "optional": true },
51
- "expo-image-picker": { "optional": true },
52
- "expo-document-picker": { "optional": true },
53
- "expo-clipboard": { "optional": true },
54
- "lucide-react-native": { "optional": true },
55
- "@radix-ui/react-dropdown-menu": { "optional": true }
45
+ "livekit-client": {
46
+ "optional": true
47
+ },
48
+ "expo-audio": {
49
+ "optional": true
50
+ },
51
+ "react-native-svg": {
52
+ "optional": true
53
+ },
54
+ "nativewind": {
55
+ "optional": true
56
+ },
57
+ "clsx": {
58
+ "optional": true
59
+ },
60
+ "tailwind-merge": {
61
+ "optional": true
62
+ },
63
+ "class-variance-authority": {
64
+ "optional": true
65
+ },
66
+ "zeego": {
67
+ "optional": true
68
+ },
69
+ "expo-image": {
70
+ "optional": true
71
+ },
72
+ "expo-image-picker": {
73
+ "optional": true
74
+ },
75
+ "expo-document-picker": {
76
+ "optional": true
77
+ },
78
+ "expo-clipboard": {
79
+ "optional": true
80
+ },
81
+ "lucide-react-native": {
82
+ "optional": true
83
+ },
84
+ "@radix-ui/react-dropdown-menu": {
85
+ "optional": true
86
+ }
56
87
  },
57
88
  "devDependencies": {
58
89
  "typescript": "^5.9.3"
@@ -0,0 +1,6 @@
1
+ import { Easing, FadeInUp } from 'react-native-reanimated';
2
+
3
+ const easeOut = Easing.bezier(0.16, 1, 0.3, 1);
4
+
5
+ export const planPreviewEnter = FadeInUp.duration(220).easing(easeOut);
6
+ export const chatMessageEnter = FadeInUp.duration(220).easing(easeOut);
@@ -9,7 +9,6 @@ import Animated, {
9
9
  withTiming,
10
10
  withSequence,
11
11
  cancelAnimation,
12
- FadeInUp,
13
12
  type SharedValue,
14
13
  } from 'react-native-reanimated';
15
14
  import {
@@ -21,13 +20,13 @@ import {
21
20
  ThumbsDown,
22
21
  Pencil,
23
22
  } from 'lucide-react-native';
24
- import { useAliaColors } from '../theme';
25
23
  import { Text } from './ui/text';
26
24
  import { AliaMarkdown } from './Markdown';
27
25
  import { ThinkingIndicator } from './ThinkingIndicator';
28
26
  import { Reasoning, ReasoningTrigger, ReasoningContent } from './Reasoning';
29
27
  import { ResearchProgressCard } from './ResearchProgressCard';
30
28
  import { PlanPreviewCard } from './PlanPreviewCard';
29
+ import { chatMessageEnter } from '../animations';
31
30
  import { getToolLabel, getToolActiveLabel, getResearchActiveLabel } from '../lib/tool-registry';
32
31
  import { getTextFromContent, getImagesFromContent } from '../lib/content-utils';
33
32
  import type { ChatMessage, ToolInvocation } from '../types';
@@ -509,7 +508,7 @@ export function AliaChatMessageList({
509
508
  return (
510
509
  <Animated.View
511
510
  key={msg.id}
512
- entering={isNewMessage ? FadeInUp.springify() : undefined}
511
+ entering={isNewMessage ? chatMessageEnter : undefined}
513
512
  >
514
513
  {msg.role === 'user' ? (
515
514
  <UserBubble
@@ -14,11 +14,10 @@
14
14
  */
15
15
 
16
16
  import React, { useRef, useState, useEffect, useCallback } from 'react';
17
- import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
17
+ import { View, Text, TouchableOpacity } from 'react-native';
18
18
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
19
19
  import { AliaChatContent, type AliaChatContentRef } from './AliaChatContent';
20
20
  import { AliaFace, type AliaExpression } from './AliaFace';
21
- import { useAliaColors } from '../theme';
22
21
  import type { WelcomeSuggestion } from './AliaWelcomeMessage';
23
22
 
24
23
  export interface AliaChatScreenProps {
@@ -50,7 +49,6 @@ export function AliaChatScreen({
50
49
  welcomeSubtitle,
51
50
  welcomeSuggestions,
52
51
  }: AliaChatScreenProps) {
53
- const colors = useAliaColors();
54
52
  const insets = useSafeAreaInsets();
55
53
  const contentRef = useRef<AliaChatContentRef>(null);
56
54
 
@@ -73,18 +71,18 @@ export function AliaChatScreen({
73
71
  }, []);
74
72
 
75
73
  return (
76
- <View style={[styles.container, { backgroundColor: colors.background, paddingTop: insets.top }]}>
74
+ <View className="flex-1 bg-background" style={{ paddingTop: insets.top }}>
77
75
  {/* Header */}
78
- <View style={styles.header}>
79
- <View style={styles.headerLeft}>
76
+ <View className="flex-row items-center justify-between px-4 py-3">
77
+ <View className="flex-row items-center gap-2.5">
80
78
  {headerLeft}
81
79
  <AliaFace size={28} expression={faceExpression} />
82
- <Text style={[styles.headerTitle, { color: colors.text }]}>Alia</Text>
80
+ <Text className="text-lg font-semibold text-foreground">Alia</Text>
83
81
  </View>
84
- <View style={styles.headerRight}>
82
+ <View className="flex-row items-center gap-2">
85
83
  {hasMessages && (
86
- <TouchableOpacity onPress={handleClear} style={styles.clearButton}>
87
- <Text style={[styles.clearText, { color: colors.secondaryText }]}>Clear</Text>
84
+ <TouchableOpacity onPress={handleClear} className="px-2.5 py-1.5">
85
+ <Text className="text-sm text-muted-foreground">Clear</Text>
88
86
  </TouchableOpacity>
89
87
  )}
90
88
  {headerRight}
@@ -105,36 +103,3 @@ export function AliaChatScreen({
105
103
  );
106
104
  }
107
105
 
108
- const styles = StyleSheet.create({
109
- container: {
110
- flex: 1,
111
- },
112
- header: {
113
- flexDirection: 'row',
114
- alignItems: 'center',
115
- justifyContent: 'space-between',
116
- paddingHorizontal: 16,
117
- paddingVertical: 12,
118
- },
119
- headerLeft: {
120
- flexDirection: 'row',
121
- alignItems: 'center',
122
- gap: 10,
123
- },
124
- headerTitle: {
125
- fontSize: 18,
126
- fontWeight: '600',
127
- },
128
- headerRight: {
129
- flexDirection: 'row',
130
- alignItems: 'center',
131
- gap: 8,
132
- },
133
- clearButton: {
134
- paddingHorizontal: 10,
135
- paddingVertical: 6,
136
- },
137
- clearText: {
138
- fontSize: 14,
139
- },
140
- });
@@ -16,6 +16,7 @@ import {
16
16
  Pressable,
17
17
  Dimensions,
18
18
  Platform,
19
+ useColorScheme,
19
20
  } from 'react-native';
20
21
  import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
21
22
  import { useKeyboardHandler } from 'react-native-keyboard-controller';
@@ -29,7 +30,6 @@ import Animated, {
29
30
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
30
31
  import { AliaChatContent, type AliaChatContentRef } from './AliaChatContent';
31
32
  import { AliaFace, type AliaExpression } from './AliaFace';
32
- import { useAliaColors } from '../theme';
33
33
  import type { WelcomeSuggestion } from './AliaWelcomeMessage';
34
34
 
35
35
  const { height: SCREEN_HEIGHT } = Dimensions.get('window');
@@ -62,8 +62,8 @@ export interface AliaChatSheetRef {
62
62
 
63
63
  export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
64
64
  ({ clientContext, model, apiUrl, welcomeGreeting, welcomeSubtitle, welcomeSuggestions }, ref) => {
65
- const colors = useAliaColors();
66
- const isDark = colors.isDark;
65
+ const scheme = useColorScheme();
66
+ const isDark = scheme === 'dark';
67
67
  const insets = useSafeAreaInsets();
68
68
 
69
69
  // Content ref for reading state
@@ -237,9 +237,9 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
237
237
  {/* Sheet */}
238
238
  <GestureDetector gesture={panGesture}>
239
239
  <Animated.View
240
+ className="bg-background"
240
241
  style={[
241
242
  styles.sheet,
242
- { backgroundColor: colors.background },
243
243
  sheetAnimStyle,
244
244
  sheetMaxHeightStyle,
245
245
  ]}
@@ -258,19 +258,14 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
258
258
  <View style={styles.header}>
259
259
  <View style={styles.headerLeft}>
260
260
  <AliaFace size={28} expression={faceExpression} />
261
- <Text style={[styles.headerTitle, { color: colors.text }]}>
261
+ <Text className="text-lg font-semibold text-foreground">
262
262
  Alia
263
263
  </Text>
264
264
  </View>
265
265
  <View style={styles.headerRight}>
266
266
  {hasMessages && (
267
267
  <TouchableOpacity onPress={handleClear} style={styles.clearButton}>
268
- <Text
269
- style={[
270
- styles.clearText,
271
- { color: colors.secondaryText },
272
- ]}
273
- >
268
+ <Text className="text-sm text-muted-foreground">
274
269
  Clear
275
270
  </Text>
276
271
  </TouchableOpacity>
@@ -280,13 +275,7 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
280
275
  style={styles.closeButton}
281
276
  hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
282
277
  >
283
- <Text
284
- style={{
285
- fontSize: 18,
286
- fontWeight: '600',
287
- color: colors.icon,
288
- }}
289
- >
278
+ <Text className="text-lg font-semibold text-muted-foreground">
290
279
  {'\u2715'}
291
280
  </Text>
292
281
  </TouchableOpacity>
@@ -318,7 +307,7 @@ AliaChatSheet.displayName = 'AliaChatSheet';
318
307
 
319
308
  const styles = StyleSheet.create({
320
309
  backdrop: {
321
- ...StyleSheet.absoluteFillObject,
310
+ ...StyleSheet.absoluteFill,
322
311
  backgroundColor: 'rgba(0,0,0,0.5)',
323
312
  },
324
313
  sheet: {
@@ -361,10 +350,6 @@ const styles = StyleSheet.create({
361
350
  alignItems: 'center',
362
351
  gap: 10,
363
352
  },
364
- headerTitle: {
365
- fontSize: 18,
366
- fontWeight: '600',
367
- },
368
353
  headerRight: {
369
354
  flexDirection: 'row',
370
355
  alignItems: 'center',
@@ -374,9 +359,6 @@ const styles = StyleSheet.create({
374
359
  paddingHorizontal: 10,
375
360
  paddingVertical: 6,
376
361
  },
377
- clearText: {
378
- fontSize: 14,
379
- },
380
362
  closeButton: {
381
363
  width: 32,
382
364
  height: 32,
@@ -1,5 +1,5 @@
1
- import React, { useEffect } from "react";
2
- import { View, Text } from "react-native";
1
+ import React, { useEffect, useMemo } from "react";
2
+ import { View, Text, Image, ImageSourcePropType } from "react-native";
3
3
  import Svg, { Path, Ellipse } from "react-native-svg";
4
4
  import Animated, {
5
5
  useSharedValue,
@@ -12,7 +12,6 @@ import Animated, {
12
12
  cancelAnimation,
13
13
  Easing,
14
14
  } from "react-native-reanimated";
15
- import { useAliaColors } from "../theme";
16
15
 
17
16
  // ─── Animated SVG wrappers ──────────────────────────────────────────────────
18
17
 
@@ -33,10 +32,19 @@ export type AliaExpression =
33
32
  | "Searching F"
34
33
  | "Looking Down";
35
34
 
35
+ export interface AliaAccessory {
36
+ layer: 'front' | 'behind';
37
+ image: ImageSourcePropType;
38
+ /** Optional position overrides (ratios 0-1, scale multiplier, rotation in degrees) */
39
+ position?: { x: number; y: number; scale: number; rotation: number };
40
+ }
41
+
36
42
  export interface AliaFaceProps {
37
43
  expression?: AliaExpression;
38
44
  size?: number;
39
45
  showLabel?: boolean;
46
+ /** Accessory overlays rendered on/around the face circle */
47
+ accessories?: AliaAccessory[];
40
48
  }
41
49
 
42
50
  // ─── Expression data (numeric decomposition of SVG paths) ───────────────────
@@ -129,11 +137,9 @@ export function AliaFace({
129
137
  expression = DEFAULT_EXPRESSION,
130
138
  size = 120,
131
139
  showLabel = false,
140
+ accessories,
132
141
  }: AliaFaceProps) {
133
- const colors = useAliaColors();
134
- const strokeColor = colors.isDark ? "#ffffff" : "#000000";
135
- const circleBg = colors.background;
136
- const circleBorder = colors.border;
142
+ const strokeColor = 'currentColor';
137
143
 
138
144
  const initial = EXPRESSIONS[DEFAULT_EXPRESSION];
139
145
 
@@ -336,64 +342,108 @@ export function AliaFace({
336
342
  transform: [{ translateY: breatheY.value }],
337
343
  }));
338
344
 
345
+ // ── Accessories ──
346
+
347
+ const { behind, front } = useMemo(() => {
348
+ if (!accessories?.length) return { behind: [] as AliaAccessory[], front: [] as AliaAccessory[] };
349
+ const b: AliaAccessory[] = [];
350
+ const f: AliaAccessory[] = [];
351
+ for (const acc of accessories) {
352
+ if (acc.layer === 'behind') b.push(acc);
353
+ else f.push(acc);
354
+ }
355
+ return { behind: b, front: f };
356
+ }, [accessories]);
357
+
358
+ const accessoryStyle = useMemo(
359
+ () => ({ position: 'absolute' as const, width: size, height: size, top: 0, left: 0 }),
360
+ [size],
361
+ );
362
+
339
363
  // ── Render ──
340
364
 
341
365
  return (
342
366
  <View style={{ alignItems: "center" }}>
343
- <View
344
- style={{
345
- width: size,
346
- height: size,
347
- borderRadius: size / 2,
348
- backgroundColor: circleBg,
349
- alignItems: "center",
350
- justifyContent: "center",
351
- borderWidth: 1,
352
- borderColor: circleBorder,
353
- }}
354
- >
355
- <Animated.View style={breatheStyle}>
356
- <Svg
357
- width={size * 0.95}
358
- height={size * 0.95}
359
- viewBox="35 35 250 250"
360
- >
361
- {/* Left brow */}
362
- <AnimatedPath
363
- animatedProps={leftBrowProps}
364
- stroke={strokeColor}
365
- strokeWidth={15}
366
- strokeLinecap="round"
367
- fill="none"
368
- />
369
-
370
- {/* Left eye */}
371
- <AnimatedEllipse
372
- animatedProps={leftEyeProps}
373
- fill={strokeColor}
374
- />
375
-
376
- {/* Right eye */}
377
- <AnimatedEllipse
378
- animatedProps={rightEyeProps}
379
- fill={strokeColor}
380
- />
381
-
382
- {/* Nose + right brow */}
383
- <AnimatedPath
384
- animatedProps={noseBrowProps}
385
- stroke={strokeColor}
386
- strokeWidth={15}
387
- strokeLinecap="round"
388
- strokeLinejoin="round"
389
- fill="none"
390
- />
391
- </Svg>
392
- </Animated.View>
367
+ <View style={{ width: size, height: size, overflow: 'visible' as const }}>
368
+ {/* Behind-layer accessories (e.g. headphones) */}
369
+ {behind.map((acc, i) => (
370
+ <Image key={`behind-${i}`} source={acc.image} style={acc.position ? {
371
+ position: 'absolute',
372
+ width: size * acc.position.scale,
373
+ height: size * acc.position.scale,
374
+ top: (acc.position.y - 0.5) * size + (size - size * acc.position.scale) / 2,
375
+ left: (acc.position.x - 0.5) * size + (size - size * acc.position.scale) / 2,
376
+ transform: [{ rotate: `${acc.position.rotation}deg` }],
377
+ } : accessoryStyle} />
378
+ ))}
379
+
380
+ {/* Face circle */}
381
+ <View
382
+ className="bg-background border-primary/20 text-foreground"
383
+ style={{
384
+ width: size,
385
+ height: size,
386
+ borderRadius: size / 2,
387
+ alignItems: "center",
388
+ justifyContent: "center",
389
+ borderWidth: 1,
390
+ }}
391
+ >
392
+ <Animated.View style={breatheStyle}>
393
+ <Svg
394
+ width={size * 0.95}
395
+ height={size * 0.95}
396
+ viewBox="35 35 250 250"
397
+ >
398
+ {/* Left brow */}
399
+ <AnimatedPath
400
+ animatedProps={leftBrowProps}
401
+ stroke={strokeColor}
402
+ strokeWidth={15}
403
+ strokeLinecap="round"
404
+ fill="none"
405
+ />
406
+
407
+ {/* Left eye */}
408
+ <AnimatedEllipse
409
+ animatedProps={leftEyeProps}
410
+ fill={strokeColor}
411
+ />
412
+
413
+ {/* Right eye */}
414
+ <AnimatedEllipse
415
+ animatedProps={rightEyeProps}
416
+ fill={strokeColor}
417
+ />
418
+
419
+ {/* Nose + right brow */}
420
+ <AnimatedPath
421
+ animatedProps={noseBrowProps}
422
+ stroke={strokeColor}
423
+ strokeWidth={15}
424
+ strokeLinecap="round"
425
+ strokeLinejoin="round"
426
+ fill="none"
427
+ />
428
+ </Svg>
429
+ </Animated.View>
430
+ </View>
431
+
432
+ {/* Front-layer accessories (e.g. hat, glasses, tie) */}
433
+ {front.map((acc, i) => (
434
+ <Image key={`front-${i}`} source={acc.image} style={acc.position ? {
435
+ position: 'absolute',
436
+ width: size * acc.position.scale,
437
+ height: size * acc.position.scale,
438
+ top: (acc.position.y - 0.5) * size + (size - size * acc.position.scale) / 2,
439
+ left: (acc.position.x - 0.5) * size + (size - size * acc.position.scale) / 2,
440
+ transform: [{ rotate: `${acc.position.rotation}deg` }],
441
+ } : accessoryStyle} />
442
+ ))}
393
443
  </View>
394
444
 
395
445
  {showLabel && (
396
- <Text style={{ fontSize: 10, color: colors.mutedForeground, marginTop: 8, textTransform: 'uppercase', letterSpacing: 3, fontWeight: '700' }}>
446
+ <Text className="text-muted-foreground" style={{ fontSize: 10, marginTop: 8, textTransform: 'uppercase', letterSpacing: 3, fontWeight: '700' }}>
397
447
  {expression}
398
448
  </Text>
399
449
  )}