@alia.onl/sdk 3.0.0 → 3.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.
@@ -0,0 +1,159 @@
1
+ import { useState } from 'react';
2
+ import { View, Pressable } from 'react-native';
3
+ import { Text } from './ui/text';
4
+ import {
5
+ Search,
6
+ BookOpen,
7
+ CheckCircle,
8
+ ChevronDown,
9
+ ChevronUp,
10
+ ExternalLink,
11
+ Loader2,
12
+ } from 'lucide-react-native';
13
+ import { useAliaColors } from '../theme';
14
+ import type { ResearchProgress } from '../types';
15
+
16
+ interface ResearchProgressCardProps {
17
+ progress: ResearchProgress;
18
+ }
19
+
20
+ const PHASE_LABELS: Record<string, string> = {
21
+ decomposing: 'Planning research',
22
+ searching: 'Searching the web',
23
+ reading: 'Reading sources',
24
+ synthesizing: 'Synthesizing report',
25
+ follow_up: 'Follow-up research',
26
+ finalizing: 'Polishing report',
27
+ complete: 'Research complete',
28
+ };
29
+
30
+ const PHASE_ORDER = ['decomposing', 'searching', 'synthesizing', 'follow_up', 'finalizing', 'complete'];
31
+
32
+ export function ResearchProgressCard({ progress }: ResearchProgressCardProps) {
33
+ const colors = useAliaColors();
34
+ const [showSources, setShowSources] = useState(false);
35
+ const [showQuestions, setShowQuestions] = useState(true);
36
+
37
+ const phaseIndex = PHASE_ORDER.indexOf(progress.phase || '');
38
+ const isComplete = progress.phase === 'complete' || progress.isComplete;
39
+
40
+ return (
41
+ <View className="mx-3 my-2 rounded-xl border border-border bg-card p-4 gap-3">
42
+ {/* Header */}
43
+ <View className="flex-row items-center gap-2">
44
+ <View className="w-8 h-8 rounded-full bg-primary/10 items-center justify-center">
45
+ {isComplete ? (
46
+ <CheckCircle size={16} color="#22c55e" />
47
+ ) : (
48
+ <Search size={16} color={colors.tint} />
49
+ )}
50
+ </View>
51
+ <View className="flex-1">
52
+ <Text className="text-sm font-semibold text-foreground">
53
+ Deep Research
54
+ </Text>
55
+ <Text className="text-xs text-muted-foreground">
56
+ {progress.message || PHASE_LABELS[progress.phase || ''] || 'Researching...'}
57
+ </Text>
58
+ </View>
59
+ {progress.sourcesFound !== undefined && (
60
+ <View className="bg-muted rounded-full px-2 py-0.5">
61
+ <Text className="text-[10px] text-muted-foreground">
62
+ {progress.sourcesFound} sources
63
+ </Text>
64
+ </View>
65
+ )}
66
+ </View>
67
+
68
+ {/* Phase progress */}
69
+ <View className="flex-row gap-1">
70
+ {PHASE_ORDER.map((phase, i) => (
71
+ <View
72
+ key={phase}
73
+ className="flex-1 h-1 rounded-full"
74
+ style={{
75
+ backgroundColor: i <= phaseIndex
76
+ ? (isComplete ? '#22c55e' : colors.tint)
77
+ : colors.muted,
78
+ }}
79
+ />
80
+ ))}
81
+ </View>
82
+
83
+ {/* Sub-questions */}
84
+ {progress.subQuestions && progress.subQuestions.length > 0 && (
85
+ <View>
86
+ <Pressable
87
+ onPress={() => setShowQuestions(!showQuestions)}
88
+ className="flex-row items-center gap-1 mb-1"
89
+ >
90
+ <BookOpen size={12} color={colors.secondaryText} />
91
+ <Text className="text-xs font-medium text-muted-foreground">
92
+ Research angles ({progress.subQuestions.length})
93
+ </Text>
94
+ {showQuestions ? (
95
+ <ChevronUp size={12} color={colors.secondaryText} />
96
+ ) : (
97
+ <ChevronDown size={12} color={colors.secondaryText} />
98
+ )}
99
+ </Pressable>
100
+ {showQuestions && (
101
+ <View className="gap-1 pl-4">
102
+ {progress.subQuestions.map((q, i) => (
103
+ <Text key={i} className="text-xs text-muted-foreground">
104
+ {i + 1}. {q}
105
+ </Text>
106
+ ))}
107
+ </View>
108
+ )}
109
+ </View>
110
+ )}
111
+
112
+ {/* Current search query indicator */}
113
+ {progress.currentQuery && !isComplete && (
114
+ <View className="flex-row items-center gap-2 bg-muted/50 rounded-lg px-3 py-1.5">
115
+ <Loader2 size={12} color={colors.secondaryText} />
116
+ <Text className="text-xs text-muted-foreground flex-1" numberOfLines={1}>
117
+ {progress.currentQuery}
118
+ </Text>
119
+ </View>
120
+ )}
121
+
122
+ {/* Sources list (collapsed by default) */}
123
+ {progress.sources && progress.sources.length > 0 && (
124
+ <View>
125
+ <Pressable
126
+ onPress={() => setShowSources(!showSources)}
127
+ className="flex-row items-center gap-1"
128
+ >
129
+ <ExternalLink size={12} color={colors.secondaryText} />
130
+ <Text className="text-xs font-medium text-muted-foreground">
131
+ Sources ({progress.sources.length})
132
+ </Text>
133
+ {showSources ? (
134
+ <ChevronUp size={12} color={colors.secondaryText} />
135
+ ) : (
136
+ <ChevronDown size={12} color={colors.secondaryText} />
137
+ )}
138
+ </Pressable>
139
+ {showSources && (
140
+ <View className="gap-1 pl-4 mt-1">
141
+ {progress.sources.map((s) => (
142
+ <Text key={s.id} className="text-xs text-primary" numberOfLines={1}>
143
+ [{s.id}] {s.title}
144
+ </Text>
145
+ ))}
146
+ </View>
147
+ )}
148
+ </View>
149
+ )}
150
+
151
+ {/* Search count */}
152
+ {progress.totalSearches !== undefined && isComplete && (
153
+ <Text className="text-[10px] text-muted-foreground text-right">
154
+ {progress.totalSearches} searches performed
155
+ </Text>
156
+ )}
157
+ </View>
158
+ );
159
+ }
@@ -1,6 +1,7 @@
1
1
  import React, { useState, useRef, useCallback, useEffect } from 'react';
2
2
  import { useOxy } from '@oxyhq/services';
3
3
  import type { ChatMessage, ToolInvocation } from '../types';
4
+ import { getTextFromContent } from '../lib/content-utils';
4
5
 
5
6
  const API_URL = process.env.EXPO_PUBLIC_ALIA_API_URL ?? 'https://api.alia.onl';
6
7
 
@@ -188,7 +189,7 @@ export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn
188
189
  // Include full conversation history with tool context
189
190
  for (const msg of messagesRef.current) {
190
191
  if (msg.role === 'system') continue;
191
- apiMessages.push({ role: msg.role, content: msg.content });
192
+ apiMessages.push({ role: msg.role, content: getTextFromContent(msg.content) });
192
193
  // Include tool results so the AI has full context
193
194
  if (msg.toolInvocations?.length) {
194
195
  for (const tool of msg.toolInvocations) {
@@ -60,18 +60,32 @@ export function useSpeechToText(options: UseSTTOptions = {}) {
60
60
  return oxyServices.httpService.getAccessToken();
61
61
  }, [options.accessToken, oxyServices]);
62
62
 
63
- // Push metering to store while recording
63
+ // Push metering to store while recording (with epsilon guard to avoid thrashing subscribers)
64
+ const lastMeteringRef = useRef(0);
64
65
  useEffect(() => {
65
- if (state === 'recording' && recorderState.metering != null) {
66
- // dBFS: -160 (silence) to 0 (max). Normalize using -60 as practical floor.
67
- const normalized = Math.min(1, Math.max(0, (recorderState.metering + 60) / 60));
68
- sttStore.getState().setMetering(normalized);
66
+ if (state === 'recording') {
67
+ let target: number;
68
+ if (recorderState.metering != null) {
69
+ // dBFS: -160 (silence) to 0 (max). Normalize using -60 as practical floor.
70
+ target = Math.min(1, Math.max(0, (recorderState.metering + 60) / 60));
71
+ } else {
72
+ // Metering unavailable — simulate gentle activity
73
+ target = 0.12 + Math.random() * 0.18;
74
+ }
75
+ if (Math.abs(target - lastMeteringRef.current) >= 0.02) {
76
+ lastMeteringRef.current = target;
77
+ sttStore.getState().setMetering(target);
78
+ }
69
79
  }
70
- }, [state, recorderState.metering]);
80
+ }, [state, recorderState.metering, recorderState.durationMillis]);
71
81
 
72
- // Sync recording state to store
82
+ // Sync recording state to store + reset metering on stop
73
83
  useEffect(() => {
74
84
  sttStore.getState().setRecording(state === 'recording');
85
+ if (state !== 'recording') {
86
+ lastMeteringRef.current = 0;
87
+ sttStore.getState().setMetering(0);
88
+ }
75
89
  }, [state]);
76
90
 
77
91
  const startRecording = useCallback(async () => {
package/src/index.ts CHANGED
@@ -14,6 +14,9 @@ export type { AliaExpression, AliaFaceProps } from './components/AliaFace';
14
14
  export { ThinkingIndicator } from './components/ThinkingIndicator';
15
15
  export { AliaMarkdown } from './components/Markdown';
16
16
  export { Reasoning, ReasoningTrigger, ReasoningContent } from './components/Reasoning';
17
+ export { AliaWelcomeMessage } from './components/AliaWelcomeMessage';
18
+ export { ResearchProgressCard } from './components/ResearchProgressCard';
19
+ export { PlanPreviewCard } from './components/PlanPreviewCard';
17
20
 
18
21
  // ── Voice components ──
19
22
  export { AudioWaveVisualizer } from './components/voice/AudioWaveVisualizer';
@@ -39,9 +42,11 @@ export type { SoundName, SoundSources } from './hooks/useSoundEffects';
39
42
  export type {
40
43
  ChatMessage,
41
44
  ToolInvocation,
42
- AliaChatSuggestion,
45
+ WelcomeSuggestion,
43
46
  ResearchProgress,
47
+ ResearchSource,
44
48
  PendingPlan,
49
+ PlanStep,
45
50
  RoomState,
46
51
  AgentState,
47
52
  VoiceMessage,
@@ -54,6 +59,8 @@ export type { ButtonProps } from './components/ui/button';
54
59
  export { Text, TextClassContext } from './components/ui/text';
55
60
  export { ChatTextInput } from './components/ui/chat-text-input';
56
61
  export { cn, formatFileSize } from './lib/utils';
62
+ export { getToolLabel, getToolActiveLabel, getResearchActiveLabel } from './lib/tool-registry';
63
+ export { getTextFromContent, getImagesFromContent } from './lib/content-utils';
57
64
 
58
65
  // ── PromptInput ──
59
66
  export { PromptInput } from './components/ui/prompt-input/prompt-input';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Extract text from message content, whether it's a string or multi-part array.
3
+ */
4
+ export function getTextFromContent(content: string | Array<{ type: string; [key: string]: any }> | any): string {
5
+ if (typeof content === 'string') return content;
6
+ if (Array.isArray(content)) {
7
+ return content
8
+ .filter((p: any) => p.type === 'text')
9
+ .map((p: any) => p.text || '')
10
+ .join('');
11
+ }
12
+ return String(content || '');
13
+ }
14
+
15
+ /**
16
+ * Extract image URLs from multi-part message content.
17
+ */
18
+ export function getImagesFromContent(content: string | Array<{ type: string; [key: string]: any }> | any): string[] {
19
+ if (!Array.isArray(content)) return [];
20
+ return content
21
+ .filter((p: any) => p.type === 'image_url' && p.image_url?.url)
22
+ .map((p: any) => p.image_url.url);
23
+ }
@@ -0,0 +1,55 @@
1
+ interface ToolDefinition {
2
+ label: string;
3
+ category: 'search' | 'communication' | 'utility' | 'memory';
4
+ }
5
+
6
+ const TOOL_REGISTRY: Record<string, ToolDefinition> = {
7
+ // Search
8
+ webSearch: { label: 'Searching the web', category: 'search' },
9
+ scrapeURL: { label: 'Reading URL', category: 'search' },
10
+ getTimeline: { label: 'Getting timeline', category: 'search' },
11
+ searchKnowledgeBase: { label: 'Searching knowledge base', category: 'search' },
12
+ webScraper: { label: 'Reading web page', category: 'search' },
13
+ browse: { label: 'Browsing the web', category: 'search' },
14
+
15
+ // Communication
16
+ sendWhatsAppMessage: { label: 'Sending WhatsApp message', category: 'communication' },
17
+ getWhatsAppChats: { label: 'Loading WhatsApp chats', category: 'communication' },
18
+ getWhatsAppMessages: { label: 'Reading WhatsApp messages', category: 'communication' },
19
+ sendTelegram: { label: 'Sending Telegram message', category: 'communication' },
20
+
21
+ // Utility
22
+ getCurrentDate: { label: 'Getting current date', category: 'utility' },
23
+ generateFile: { label: 'Generating file', category: 'utility' },
24
+
25
+ // Memory
26
+ saveUserMemory: { label: 'Saving to memory', category: 'memory' },
27
+ updateUserPreferences: { label: 'Updating preferences', category: 'memory' },
28
+ updateUserContext: { label: 'Updating context', category: 'memory' },
29
+ };
30
+
31
+ export function getToolLabel(toolName: string): string {
32
+ return TOOL_REGISTRY[toolName]?.label || toolName;
33
+ }
34
+
35
+ export function getToolActiveLabel(toolName: string): string | undefined {
36
+ const label = TOOL_REGISTRY[toolName]?.label;
37
+ if (!label) return undefined;
38
+ return label + '...';
39
+ }
40
+
41
+ /** Status strings for the ThinkingIndicator during research phases. */
42
+ const RESEARCH_ACTIVE_LABELS: Record<string, string> = {
43
+ decomposing: 'Decomposing query...',
44
+ searching: 'Searching sources...',
45
+ reading: 'Reading articles...',
46
+ synthesizing: 'Synthesizing findings...',
47
+ follow_up: 'Following up...',
48
+ finalizing: 'Finalizing research...',
49
+ };
50
+
51
+ export function getResearchActiveLabel(phase: string): string | undefined {
52
+ return RESEARCH_ACTIVE_LABELS[phase];
53
+ }
54
+
55
+ export { TOOL_REGISTRY };
package/src/types.ts CHANGED
@@ -1,5 +1,19 @@
1
+ // ── Welcome / Suggestion Types ──
2
+
3
+ export interface WelcomeSuggestion {
4
+ id: string;
5
+ title: string;
6
+ description: string;
7
+ }
8
+
1
9
  // ── Chat Message Types ──
2
10
 
11
+ export interface ResearchSource {
12
+ id: number;
13
+ url: string;
14
+ title: string;
15
+ }
16
+
3
17
  export interface ResearchProgress {
4
18
  phase?: string;
5
19
  message?: string;
@@ -7,6 +21,9 @@ export interface ResearchProgress {
7
21
  sourcesFound?: number;
8
22
  currentQuery?: string;
9
23
  iteration?: number;
24
+ isComplete?: boolean;
25
+ sources?: ResearchSource[];
26
+ totalSearches?: number;
10
27
  }
11
28
 
12
29
  export interface PlanStep {
@@ -25,7 +42,7 @@ export interface PendingPlan {
25
42
  export interface ChatMessage {
26
43
  id: string;
27
44
  role: 'user' | 'assistant' | 'system';
28
- content: string;
45
+ content: string | Array<{ type: string; [key: string]: any }>;
29
46
  toolInvocations?: ToolInvocation[];
30
47
  createdAt: number;
31
48
  /** Extended thinking / reasoning content */
@@ -42,6 +59,8 @@ export interface ChatMessage {
42
59
  isStreaming?: boolean;
43
60
  /** Cached TTS audio URL for read-aloud */
44
61
  audioUrl?: string;
62
+ /** Delegated agent identity */
63
+ agentInfo?: { id: string; name: string; avatar: string | null };
45
64
  }
46
65
 
47
66
  export interface ToolInvocation {
@@ -53,12 +72,6 @@ export interface ToolInvocation {
53
72
  toolCallId?: string;
54
73
  }
55
74
 
56
- export interface AliaChatSuggestion {
57
- label: string;
58
- icon?: string;
59
- prompt: string;
60
- }
61
-
62
75
  // ── Voice Types ──
63
76
 
64
77
  export type RoomState = 'disconnected' | 'connecting' | 'connected' | 'error';
@@ -1,70 +0,0 @@
1
- import React from 'react';
2
- import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
3
- import { useAliaColors } from '../theme';
4
- import type { AliaChatSuggestion } from '../types';
5
-
6
- interface AliaChatSuggestionsProps {
7
- suggestions: AliaChatSuggestion[];
8
- onSelect: (prompt: string) => void;
9
- }
10
-
11
- export function AliaChatSuggestions({ suggestions, onSelect }: AliaChatSuggestionsProps) {
12
- const colors = useAliaColors();
13
- const isDark = colors.isDark;
14
-
15
- if (suggestions.length === 0) return null;
16
-
17
- return (
18
- <View style={styles.container}>
19
- <Text style={[styles.greeting, { color: colors.tint }]}>
20
- How can I help you today?
21
- </Text>
22
- <View style={styles.list}>
23
- {suggestions.map((suggestion, i) => (
24
- <TouchableOpacity
25
- key={i}
26
- style={[
27
- styles.chip,
28
- { backgroundColor: isDark ? colors.card : colors.card },
29
- ]}
30
- onPress={() => onSelect(suggestion.prompt)}
31
- activeOpacity={0.7}
32
- >
33
- <Text style={[styles.chipText, { color: colors.text }]}>
34
- {suggestion.label}
35
- </Text>
36
- </TouchableOpacity>
37
- ))}
38
- </View>
39
- </View>
40
- );
41
- }
42
-
43
- const styles = StyleSheet.create({
44
- container: {
45
- paddingTop: 16,
46
- paddingHorizontal: 16,
47
- },
48
- greeting: {
49
- fontSize: 24,
50
- fontWeight: '600',
51
- lineHeight: 32,
52
- marginBottom: 20,
53
- paddingHorizontal: 4,
54
- },
55
- list: {
56
- gap: 8,
57
- },
58
- chip: {
59
- flexDirection: 'row',
60
- alignItems: 'center',
61
- gap: 12,
62
- paddingHorizontal: 16,
63
- paddingVertical: 14,
64
- borderRadius: 16,
65
- },
66
- chipText: {
67
- fontSize: 14,
68
- flex: 1,
69
- },
70
- });