@alia.onl/sdk 3.0.0 → 3.1.1

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.
@@ -14,18 +14,15 @@
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
- import type { AliaChatSuggestion } from '../types';
21
+ import type { WelcomeSuggestion } from './AliaWelcomeMessage';
23
22
 
24
23
  export interface AliaChatScreenProps {
25
24
  /** App context injected into system prompt */
26
25
  clientContext?: string;
27
- /** Quick action suggestions shown when chat is empty */
28
- suggestions?: AliaChatSuggestion[];
29
26
  /** Alia model (default: 'alia-v1') */
30
27
  model?: string;
31
28
  /** API URL override */
@@ -34,17 +31,24 @@ export interface AliaChatScreenProps {
34
31
  headerLeft?: React.ReactNode;
35
32
  /** Optional header right action (e.g., settings button) */
36
33
  headerRight?: React.ReactNode;
34
+ /** Welcome screen greeting */
35
+ welcomeGreeting?: string;
36
+ /** Welcome screen subtitle */
37
+ welcomeSubtitle?: string;
38
+ /** Welcome screen suggestions */
39
+ welcomeSuggestions?: WelcomeSuggestion[];
37
40
  }
38
41
 
39
42
  export function AliaChatScreen({
40
43
  clientContext,
41
- suggestions = [],
42
44
  model,
43
45
  apiUrl,
44
46
  headerLeft,
45
47
  headerRight,
48
+ welcomeGreeting,
49
+ welcomeSubtitle,
50
+ welcomeSuggestions,
46
51
  }: AliaChatScreenProps) {
47
- const colors = useAliaColors();
48
52
  const insets = useSafeAreaInsets();
49
53
  const contentRef = useRef<AliaChatContentRef>(null);
50
54
 
@@ -67,18 +71,18 @@ export function AliaChatScreen({
67
71
  }, []);
68
72
 
69
73
  return (
70
- <View style={[styles.container, { backgroundColor: colors.background, paddingTop: insets.top }]}>
74
+ <View className="flex-1 bg-background" style={{ paddingTop: insets.top }}>
71
75
  {/* Header */}
72
- <View style={styles.header}>
73
- <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">
74
78
  {headerLeft}
75
79
  <AliaFace size={28} expression={faceExpression} />
76
- <Text style={[styles.headerTitle, { color: colors.text }]}>Alia</Text>
80
+ <Text className="text-lg font-semibold text-foreground">Alia</Text>
77
81
  </View>
78
- <View style={styles.headerRight}>
82
+ <View className="flex-row items-center gap-2">
79
83
  {hasMessages && (
80
- <TouchableOpacity onPress={handleClear} style={styles.clearButton}>
81
- <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>
82
86
  </TouchableOpacity>
83
87
  )}
84
88
  {headerRight}
@@ -89,44 +93,13 @@ export function AliaChatScreen({
89
93
  <AliaChatContent
90
94
  ref={contentRef}
91
95
  clientContext={clientContext}
92
- suggestions={suggestions}
93
96
  model={model}
94
97
  apiUrl={apiUrl}
98
+ welcomeGreeting={welcomeGreeting}
99
+ welcomeSubtitle={welcomeSubtitle}
100
+ welcomeSuggestions={welcomeSuggestions}
95
101
  />
96
102
  </View>
97
103
  );
98
104
  }
99
105
 
100
- const styles = StyleSheet.create({
101
- container: {
102
- flex: 1,
103
- },
104
- header: {
105
- flexDirection: 'row',
106
- alignItems: 'center',
107
- justifyContent: 'space-between',
108
- paddingHorizontal: 16,
109
- paddingVertical: 12,
110
- },
111
- headerLeft: {
112
- flexDirection: 'row',
113
- alignItems: 'center',
114
- gap: 10,
115
- },
116
- headerTitle: {
117
- fontSize: 18,
118
- fontWeight: '600',
119
- },
120
- headerRight: {
121
- flexDirection: 'row',
122
- alignItems: 'center',
123
- gap: 8,
124
- },
125
- clearButton: {
126
- paddingHorizontal: 10,
127
- paddingVertical: 6,
128
- },
129
- clearText: {
130
- fontSize: 14,
131
- },
132
- });
@@ -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,8 +30,7 @@ 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
- import type { AliaChatSuggestion } from '../types';
33
+ import type { WelcomeSuggestion } from './AliaWelcomeMessage';
34
34
 
35
35
  const { height: SCREEN_HEIGHT } = Dimensions.get('window');
36
36
 
@@ -43,12 +43,16 @@ const SPRING_CONFIG = {
43
43
  export interface AliaChatSheetProps {
44
44
  /** App context injected into system prompt */
45
45
  clientContext?: string;
46
- /** Quick action suggestions shown when chat is empty */
47
- suggestions?: AliaChatSuggestion[];
48
46
  /** Alia model (default: 'alia-v1') */
49
47
  model?: string;
50
48
  /** API URL override */
51
49
  apiUrl?: string;
50
+ /** Welcome screen greeting */
51
+ welcomeGreeting?: string;
52
+ /** Welcome screen subtitle */
53
+ welcomeSubtitle?: string;
54
+ /** Welcome screen suggestions */
55
+ welcomeSuggestions?: WelcomeSuggestion[];
52
56
  }
53
57
 
54
58
  export interface AliaChatSheetRef {
@@ -57,9 +61,9 @@ export interface AliaChatSheetRef {
57
61
  }
58
62
 
59
63
  export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
60
- ({ clientContext, suggestions = [], model, apiUrl }, ref) => {
61
- const colors = useAliaColors();
62
- const isDark = colors.isDark;
64
+ ({ clientContext, model, apiUrl, welcomeGreeting, welcomeSubtitle, welcomeSuggestions }, ref) => {
65
+ const scheme = useColorScheme();
66
+ const isDark = scheme === 'dark';
63
67
  const insets = useSafeAreaInsets();
64
68
 
65
69
  // Content ref for reading state
@@ -233,9 +237,9 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
233
237
  {/* Sheet */}
234
238
  <GestureDetector gesture={panGesture}>
235
239
  <Animated.View
240
+ className="bg-background"
236
241
  style={[
237
242
  styles.sheet,
238
- { backgroundColor: colors.background },
239
243
  sheetAnimStyle,
240
244
  sheetMaxHeightStyle,
241
245
  ]}
@@ -254,19 +258,14 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
254
258
  <View style={styles.header}>
255
259
  <View style={styles.headerLeft}>
256
260
  <AliaFace size={28} expression={faceExpression} />
257
- <Text style={[styles.headerTitle, { color: colors.text }]}>
261
+ <Text className="text-lg font-semibold text-foreground">
258
262
  Alia
259
263
  </Text>
260
264
  </View>
261
265
  <View style={styles.headerRight}>
262
266
  {hasMessages && (
263
267
  <TouchableOpacity onPress={handleClear} style={styles.clearButton}>
264
- <Text
265
- style={[
266
- styles.clearText,
267
- { color: colors.secondaryText },
268
- ]}
269
- >
268
+ <Text className="text-sm text-muted-foreground">
270
269
  Clear
271
270
  </Text>
272
271
  </TouchableOpacity>
@@ -276,13 +275,7 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
276
275
  style={styles.closeButton}
277
276
  hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
278
277
  >
279
- <Text
280
- style={{
281
- fontSize: 18,
282
- fontWeight: '600',
283
- color: colors.icon,
284
- }}
285
- >
278
+ <Text className="text-lg font-semibold text-muted-foreground">
286
279
  {'\u2715'}
287
280
  </Text>
288
281
  </TouchableOpacity>
@@ -294,10 +287,12 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
294
287
  <AliaChatContent
295
288
  ref={contentRef}
296
289
  clientContext={clientContext}
297
- suggestions={suggestions}
298
290
  model={model}
299
291
  apiUrl={apiUrl}
300
292
  scrollOffsetY={scrollOffsetY}
293
+ welcomeGreeting={welcomeGreeting}
294
+ welcomeSubtitle={welcomeSubtitle}
295
+ welcomeSuggestions={welcomeSuggestions}
301
296
  />
302
297
  </GestureDetector>
303
298
  </Animated.View>
@@ -312,7 +307,7 @@ AliaChatSheet.displayName = 'AliaChatSheet';
312
307
 
313
308
  const styles = StyleSheet.create({
314
309
  backdrop: {
315
- ...StyleSheet.absoluteFillObject,
310
+ ...StyleSheet.absoluteFill,
316
311
  backgroundColor: 'rgba(0,0,0,0.5)',
317
312
  },
318
313
  sheet: {
@@ -355,10 +350,6 @@ const styles = StyleSheet.create({
355
350
  alignItems: 'center',
356
351
  gap: 10,
357
352
  },
358
- headerTitle: {
359
- fontSize: 18,
360
- fontWeight: '600',
361
- },
362
353
  headerRight: {
363
354
  flexDirection: 'row',
364
355
  alignItems: 'center',
@@ -368,9 +359,6 @@ const styles = StyleSheet.create({
368
359
  paddingHorizontal: 10,
369
360
  paddingVertical: 6,
370
361
  },
371
- clearText: {
372
- fontSize: 14,
373
- },
374
362
  closeButton: {
375
363
  width: 32,
376
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
  )}
@@ -0,0 +1,75 @@
1
+ import { Pressable, StyleSheet, View } from 'react-native';
2
+ import { BlurView } from 'expo-blur';
3
+ import { Text } from './ui/text';
4
+ import Animated, { FadeIn } from 'react-native-reanimated';
5
+ import { AliaFace } from './AliaFace';
6
+ import type { WelcomeSuggestion } from '../types';
7
+
8
+ export type { WelcomeSuggestion };
9
+
10
+ export interface AliaWelcomeMessageProps {
11
+ greeting: string;
12
+ subtitle?: string;
13
+ suggestions?: WelcomeSuggestion[];
14
+ onSuggestionPress?: (text: string) => void;
15
+ faceSize?: number;
16
+ }
17
+
18
+ export function AliaWelcomeMessage({
19
+ greeting,
20
+ subtitle,
21
+ suggestions = [],
22
+ onSuggestionPress,
23
+ faceSize = 64,
24
+ }: AliaWelcomeMessageProps) {
25
+ return (
26
+ <View className="flex-1 items-center justify-center px-4">
27
+ <View className="w-full max-w-2xl">
28
+ {/* Face + Title */}
29
+ <View className="items-start mb-8">
30
+ <View className="mb-4">
31
+ <AliaFace size={faceSize} expression="Greeting" />
32
+ </View>
33
+ <View className="space-y-2">
34
+ <Text className="text-3xl font-bold tracking-tight text-foreground">
35
+ {greeting}
36
+ </Text>
37
+ {subtitle ? (
38
+ <Text className="text-xl font-medium text-muted-foreground">
39
+ {subtitle}
40
+ </Text>
41
+ ) : null}
42
+ </View>
43
+ </View>
44
+
45
+ {/* Suggestion Grid */}
46
+ {suggestions.length > 0 && (
47
+ <Animated.View entering={FadeIn.duration(400)}>
48
+ <View className="flex-row flex-wrap gap-2">
49
+ {suggestions.map((item) => (
50
+ <Pressable
51
+ key={item.id}
52
+ className="flex-1 min-w-[35%] flex-col items-start rounded-3xl border border-border overflow-hidden active:bg-muted/50"
53
+ onPress={() => onSuggestionPress?.(item.description)}
54
+ >
55
+ <BlurView intensity={60} tint="default" style={StyleSheet.absoluteFill} />
56
+ <View className="p-4 w-full">
57
+ <Text className="text-sm font-medium text-surface-foreground mb-1" numberOfLines={1}>
58
+ {item.title}
59
+ </Text>
60
+ <Text
61
+ className="text-xs text-muted-foreground line-clamp-1"
62
+ numberOfLines={1}
63
+ >
64
+ {item.description}
65
+ </Text>
66
+ </View>
67
+ </Pressable>
68
+ ))}
69
+ </View>
70
+ </Animated.View>
71
+ )}
72
+ </View>
73
+ </View>
74
+ );
75
+ }