@alia.onl/sdk 2.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.
Files changed (37) hide show
  1. package/package.json +25 -3
  2. package/src/components/AliaChatContent.tsx +356 -0
  3. package/src/components/AliaChatInput.tsx +173 -26
  4. package/src/components/AliaChatMessageList.tsx +459 -168
  5. package/src/components/AliaChatScreen.tsx +140 -0
  6. package/src/components/AliaChatSheet.tsx +41 -44
  7. package/src/components/AliaWelcomeMessage.tsx +75 -0
  8. package/src/components/Markdown.tsx +58 -114
  9. package/src/components/PlanPreviewCard.tsx +76 -0
  10. package/src/components/ResearchProgressCard.tsx +159 -0
  11. package/src/components/ui/button.tsx +104 -0
  12. package/src/components/ui/chat-text-input.tsx +164 -0
  13. package/src/components/ui/dropdown-menu.tsx +2 -0
  14. package/src/components/ui/dropdown-menu.web.tsx +355 -0
  15. package/src/components/ui/prompt-input/actions.tsx +28 -0
  16. package/src/components/ui/prompt-input/add-menu.tsx +83 -0
  17. package/src/components/ui/prompt-input/attachments.tsx +183 -0
  18. package/src/components/ui/prompt-input/autocomplete.tsx +168 -0
  19. package/src/components/ui/prompt-input/context.tsx +71 -0
  20. package/src/components/ui/prompt-input/mic-button.tsx +53 -0
  21. package/src/components/ui/prompt-input/prompt-input.tsx +276 -0
  22. package/src/components/ui/prompt-input/submit-button.tsx +49 -0
  23. package/src/components/ui/prompt-input/textarea.tsx +64 -0
  24. package/src/components/ui/text.tsx +29 -0
  25. package/src/hooks/useAliaChat.ts +18 -8
  26. package/src/hooks/useDocumentPicker.ts +37 -0
  27. package/src/hooks/useImagePicker.ts +38 -0
  28. package/src/hooks/useSpeechToText.ts +25 -9
  29. package/src/index.ts +41 -3
  30. package/src/lib/content-utils.ts +23 -0
  31. package/src/lib/keyboard.native.tsx +6 -0
  32. package/src/lib/keyboard.tsx +25 -0
  33. package/src/lib/tool-registry.ts +55 -0
  34. package/src/lib/utils.ts +14 -0
  35. package/src/optional-deps.d.ts +17 -0
  36. package/src/types.ts +27 -10
  37. package/src/components/AliaChatSuggestions.tsx +0 -70
@@ -0,0 +1,29 @@
1
+ import * as React from "react";
2
+ import { Text as RNText, type TextProps as RNTextProps } from "react-native";
3
+ import { cn } from "../../lib/utils";
4
+
5
+ const TextClassContext = React.createContext<string | undefined>(undefined);
6
+
7
+ export type TextProps = RNTextProps & {
8
+ className?: string;
9
+ };
10
+
11
+ const Text = React.forwardRef<RNText, TextProps>(
12
+ ({ className, ...props }, ref) => {
13
+ const textClass = React.useContext(TextClassContext);
14
+ return (
15
+ <RNText
16
+ className={cn(
17
+ "text-base leading-7 text-foreground web:select-text font-sans",
18
+ textClass,
19
+ className
20
+ )}
21
+ ref={ref}
22
+ {...props}
23
+ />
24
+ );
25
+ }
26
+ );
27
+ Text.displayName = "Text";
28
+
29
+ export { Text, TextClassContext };
@@ -1,6 +1,7 @@
1
- import { useState, useRef, useCallback, useEffect } from 'react';
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
 
@@ -17,8 +18,10 @@ export interface UseAliaChatOptions {
17
18
 
18
19
  export interface UseAliaChatReturn {
19
20
  messages: ChatMessage[];
21
+ setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
20
22
  send: (text: string) => void;
21
23
  isStreaming: boolean;
24
+ stop: () => void;
22
25
  clear: () => void;
23
26
  error: string | null;
24
27
  }
@@ -186,7 +189,7 @@ export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn
186
189
  // Include full conversation history with tool context
187
190
  for (const msg of messagesRef.current) {
188
191
  if (msg.role === 'system') continue;
189
- apiMessages.push({ role: msg.role, content: msg.content });
192
+ apiMessages.push({ role: msg.role, content: getTextFromContent(msg.content) });
190
193
  // Include tool results so the AI has full context
191
194
  if (msg.toolInvocations?.length) {
192
195
  for (const tool of msg.toolInvocations) {
@@ -312,8 +315,6 @@ export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn
312
315
  updateAssistant(() => ({
313
316
  pendingPlan: {
314
317
  planId: parsed.planId,
315
- intent: parsed.intent,
316
- confidence: parsed.confidence,
317
318
  steps: parsed.steps || [],
318
319
  approved: false,
319
320
  rejected: false,
@@ -431,18 +432,27 @@ export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn
431
432
  [isStreaming, getToken, apiUrl, model, clientContext, scheduleFlush, flushPendingUpdates, updateAssistant, applyToolResult],
432
433
  );
433
434
 
434
- const clear = useCallback(() => {
435
+ const stop = useCallback(() => {
435
436
  if (abortRef.current) {
436
437
  abortRef.current.abort();
437
438
  abortRef.current = null;
438
439
  }
439
- setMessages([]);
440
+ flushPendingUpdates();
441
+ if (flushTimerRef.current) {
442
+ clearTimeout(flushTimerRef.current);
443
+ flushTimerRef.current = null;
444
+ }
440
445
  setIsStreaming(false);
446
+ }, [flushPendingUpdates]);
447
+
448
+ const clear = useCallback(() => {
449
+ stop();
450
+ setMessages([]);
441
451
  setError(null);
442
452
  pendingContentRef.current = '';
443
453
  pendingReasoningRef.current = '';
444
454
  toolInvocationsRef.current = [];
445
- }, []);
455
+ }, [stop]);
446
456
 
447
- return { messages, send, isStreaming, clear, error };
457
+ return { messages, setMessages, send, isStreaming, stop, clear, error };
448
458
  }
@@ -0,0 +1,37 @@
1
+ import * as DocumentPicker from 'expo-document-picker';
2
+
3
+ export type DocumentPickerResult = {
4
+ uri: string;
5
+ name: string;
6
+ size: number;
7
+ mimeType: string;
8
+ };
9
+
10
+ type UseDocumentPickerResult = {
11
+ pickDocument: () => Promise<DocumentPickerResult[] | undefined>;
12
+ };
13
+
14
+ export function useDocumentPicker(): UseDocumentPickerResult {
15
+ const pickDocument = async (): Promise<DocumentPickerResult[] | undefined> => {
16
+ try {
17
+ const result = await DocumentPicker.getDocumentAsync({
18
+ type: '*/*',
19
+ multiple: true,
20
+ copyToCacheDirectory: true,
21
+ });
22
+
23
+ if (result.canceled === false && result.assets && result.assets.length > 0) {
24
+ return result.assets.map((asset: any) => ({
25
+ uri: asset.uri,
26
+ name: asset.name,
27
+ size: asset.size || 0,
28
+ mimeType: asset.mimeType || 'application/octet-stream',
29
+ }));
30
+ }
31
+ } catch (error) {
32
+ console.warn('Failed to pick document:', error);
33
+ }
34
+ };
35
+
36
+ return { pickDocument };
37
+ }
@@ -0,0 +1,38 @@
1
+ import * as ImagePicker from 'expo-image-picker';
2
+
3
+ export type ImagePickerAsset = {
4
+ uri: string;
5
+ name: string;
6
+ size: number;
7
+ mimeType: string;
8
+ };
9
+
10
+ type ImagePickerResult = {
11
+ pickImage: () => Promise<ImagePickerAsset[] | undefined>;
12
+ };
13
+
14
+ export function useImagePicker(): ImagePickerResult {
15
+ const pickImage = async (): Promise<ImagePickerAsset[] | undefined> => {
16
+ try {
17
+ const result = await ImagePicker.launchImageLibraryAsync({
18
+ allowsMultipleSelection: true,
19
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
20
+ aspect: [4, 3],
21
+ quality: 1,
22
+ });
23
+
24
+ if (!result.canceled && result.assets.length > 0) {
25
+ return result.assets.map((asset: any) => ({
26
+ uri: asset.uri,
27
+ name: asset.fileName || `image-${Date.now()}.jpg`,
28
+ size: asset.fileSize || 0,
29
+ mimeType: asset.mimeType || 'image/jpeg',
30
+ }));
31
+ }
32
+ } catch (error) {
33
+ console.warn('Failed to pick image:', error);
34
+ }
35
+ };
36
+
37
+ return { pickImage };
38
+ }
@@ -27,7 +27,7 @@ interface STTStoreState {
27
27
  setMetering: (v: number) => void;
28
28
  }
29
29
 
30
- const useSTTStore = create<STTStoreState>((set) => ({
30
+ export const useSTTStore = create<STTStoreState>((set) => ({
31
31
  isRecording: false,
32
32
  metering: 0,
33
33
  setRecording: (isRecording) => set({ isRecording }),
@@ -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 () => {
@@ -119,6 +133,8 @@ export function useSpeechToText(options: UseSTTOptions = {}) {
119
133
  // Read audio file as base64
120
134
  const response = await fetch(uri);
121
135
  const blob = await response.blob();
136
+ // Detect actual MIME type (web records webm, native records m4a)
137
+ const detectedFormat = blob.type?.split(';')[0] || 'audio/m4a';
122
138
  const base64 = await new Promise<string>((resolve, reject) => {
123
139
  const reader = new FileReader();
124
140
  reader.onload = () => {
@@ -139,7 +155,7 @@ export function useSpeechToText(options: UseSTTOptions = {}) {
139
155
  },
140
156
  body: JSON.stringify({
141
157
  audio: base64,
142
- format: 'audio/m4a',
158
+ format: detectedFormat,
143
159
  }),
144
160
  });
145
161
 
package/src/index.ts CHANGED
@@ -2,9 +2,11 @@
2
2
  // @alia.onl/sdk — Alia AI Chat SDK
3
3
  // Reusable chat UI, voice, and streaming components for Oxy apps
4
4
 
5
- // ── Main component ──
5
+ // ── Main components ──
6
6
  export { AliaChatSheet } from './components/AliaChatSheet';
7
7
  export type { AliaChatSheetProps, AliaChatSheetRef } from './components/AliaChatSheet';
8
+ export { AliaChatScreen } from './components/AliaChatScreen';
9
+ export type { AliaChatScreenProps } from './components/AliaChatScreen';
8
10
 
9
11
  // ── Chat UI components ──
10
12
  export { AliaFace } from './components/AliaFace';
@@ -12,6 +14,9 @@ export type { AliaExpression, AliaFaceProps } from './components/AliaFace';
12
14
  export { ThinkingIndicator } from './components/ThinkingIndicator';
13
15
  export { AliaMarkdown } from './components/Markdown';
14
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';
15
20
 
16
21
  // ── Voice components ──
17
22
  export { AudioWaveVisualizer } from './components/voice/AudioWaveVisualizer';
@@ -28,7 +33,7 @@ export { useAudioLevelMonitor } from './hooks/useAudioLevelMonitor';
28
33
  export { useAudioLevels } from './hooks/useAudioLevels';
29
34
  export { useTTS } from './hooks/useTTS';
30
35
  export type { UseTTSOptions } from './hooks/useTTS';
31
- export { useSpeechToText } from './hooks/useSpeechToText';
36
+ export { useSpeechToText, useSTTStore } from './hooks/useSpeechToText';
32
37
  export type { UseSTTOptions } from './hooks/useSpeechToText';
33
38
  export { useSoundEffects, useVoiceSoundEffects } from './hooks/useSoundEffects';
34
39
  export type { SoundName, SoundSources } from './hooks/useSoundEffects';
@@ -37,15 +42,48 @@ export type { SoundName, SoundSources } from './hooks/useSoundEffects';
37
42
  export type {
38
43
  ChatMessage,
39
44
  ToolInvocation,
40
- AliaChatSuggestion,
45
+ WelcomeSuggestion,
41
46
  ResearchProgress,
47
+ ResearchSource,
42
48
  PendingPlan,
49
+ PlanStep,
43
50
  RoomState,
44
51
  AgentState,
45
52
  VoiceMessage,
46
53
  VoiceToolInvocation,
47
54
  } from './types';
48
55
 
56
+ // ── UI Primitives (NativeWind) ──
57
+ export { Button, buttonVariants, buttonTextVariants } from './components/ui/button';
58
+ export type { ButtonProps } from './components/ui/button';
59
+ export { Text, TextClassContext } from './components/ui/text';
60
+ export { ChatTextInput } from './components/ui/chat-text-input';
61
+ export { cn, formatFileSize } from './lib/utils';
62
+ export { getToolLabel, getToolActiveLabel, getResearchActiveLabel } from './lib/tool-registry';
63
+ export { getTextFromContent, getImagesFromContent } from './lib/content-utils';
64
+
65
+ // ── PromptInput ──
66
+ export { PromptInput } from './components/ui/prompt-input/prompt-input';
67
+ export type { PromptInputProps } from './components/ui/prompt-input/prompt-input';
68
+ export { usePromptInput, useIsFullscreen, PromptInputContext } from './components/ui/prompt-input/context';
69
+ export type { Attachment, Completion, PromptInputContextType } from './components/ui/prompt-input/context';
70
+ export { PromptInputTextarea } from './components/ui/prompt-input/textarea';
71
+ export { PromptInputActions } from './components/ui/prompt-input/actions';
72
+ export { PromptInputSubmitButton } from './components/ui/prompt-input/submit-button';
73
+ export { PromptInputMicButton } from './components/ui/prompt-input/mic-button';
74
+ export { PromptInputAddMenu } from './components/ui/prompt-input/add-menu';
75
+ export { PromptInputAttachments } from './components/ui/prompt-input/attachments';
76
+ export { PromptInputAutocomplete } from './components/ui/prompt-input/autocomplete';
77
+
78
+ // ── Picker hooks ──
79
+ export { useImagePicker } from './hooks/useImagePicker';
80
+ export type { ImagePickerAsset } from './hooks/useImagePicker';
81
+ export { useDocumentPicker } from './hooks/useDocumentPicker';
82
+ export type { DocumentPickerResult } from './hooks/useDocumentPicker';
83
+
84
+ // ── Keyboard ──
85
+ export { KeyboardAwareScrollView, KeyboardAvoidingView, KeyboardProvider } from './lib/keyboard';
86
+
49
87
  // ── Theme ──
50
88
  export { useAliaColors } from './theme';
51
89
  export type { AliaColors } from './theme';
@@ -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,6 @@
1
+ // Native: re-export react-native-keyboard-controller components
2
+ export {
3
+ KeyboardAwareScrollView,
4
+ KeyboardAvoidingView,
5
+ KeyboardProvider,
6
+ } from 'react-native-keyboard-controller';
@@ -0,0 +1,25 @@
1
+ // Web: re-export React Native built-in components as keyboard-controller substitutes
2
+ import React from 'react';
3
+ import { ScrollView, type ScrollViewProps, KeyboardAvoidingView } from 'react-native';
4
+
5
+ // Accept native-only props so shared components don't cause TS errors
6
+ type KeyboardAwareScrollViewProps = ScrollViewProps & {
7
+ bottomOffset?: number;
8
+ disableScrollOnKeyboardHide?: boolean;
9
+ enabled?: boolean;
10
+ extraKeyboardSpace?: number;
11
+ };
12
+
13
+ const KeyboardAwareScrollView = React.forwardRef<ScrollView, KeyboardAwareScrollViewProps>(
14
+ ({ bottomOffset, disableScrollOnKeyboardHide, enabled, extraKeyboardSpace, ...props }, ref) => (
15
+ <ScrollView ref={ref} {...props} />
16
+ )
17
+ );
18
+ KeyboardAwareScrollView.displayName = 'KeyboardAwareScrollView';
19
+
20
+ export { KeyboardAwareScrollView, KeyboardAvoidingView };
21
+
22
+ // No-op provider on web — keyboard-controller is native-only
23
+ export function KeyboardProvider({ children }: { children: React.ReactNode }) {
24
+ return <>{children}</>;
25
+ }
@@ -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 };
@@ -0,0 +1,14 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export function formatFileSize(bytes: number): string {
9
+ if (bytes === 0) return '0 B';
10
+ const k = 1024;
11
+ const sizes = ['B', 'KB', 'MB', 'GB'];
12
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
13
+ return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
14
+ }
@@ -1,6 +1,13 @@
1
+ // NativeWind types — extends RN components with className prop
2
+ /// <reference types="nativewind/types" />
3
+
1
4
  // Type declarations for optional peer dependencies.
2
5
  // These prevent TypeScript errors when the deps aren't installed.
3
6
  // At runtime, the host app provides the actual packages.
7
+ //
8
+ // NOTE: Only declare modules for packages NOT installed in the monorepo.
9
+ // Installed packages (lucide-react-native, zeego, expo-image, etc.)
10
+ // have their own real types — ambient declarations here would shadow them.
4
11
 
5
12
  declare module 'livekit-client' {
6
13
  export class Room {
@@ -67,3 +74,13 @@ declare module 'expo-audio' {
67
74
  uri: string | null;
68
75
  }
69
76
  }
77
+
78
+ declare module 'expo-clipboard' {
79
+ export function setStringAsync(text: string): Promise<boolean>;
80
+ export function getStringAsync(): Promise<string>;
81
+ }
82
+
83
+ declare module 'expo-image-picker' {
84
+ export function launchImageLibraryAsync(options?: any): Promise<any>;
85
+ export const MediaTypeOptions: { Images: string; Videos: string; All: string };
86
+ }
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,13 +21,20 @@ export interface ResearchProgress {
7
21
  sourcesFound?: number;
8
22
  currentQuery?: string;
9
23
  iteration?: number;
24
+ isComplete?: boolean;
25
+ sources?: ResearchSource[];
26
+ totalSearches?: number;
27
+ }
28
+
29
+ export interface PlanStep {
30
+ action: string;
31
+ description: string;
32
+ toolName?: string;
10
33
  }
11
34
 
12
35
  export interface PendingPlan {
13
36
  planId: string;
14
- intent: string;
15
- confidence?: number;
16
- steps: string[];
37
+ steps: PlanStep[];
17
38
  approved: boolean;
18
39
  rejected: boolean;
19
40
  }
@@ -21,7 +42,7 @@ export interface PendingPlan {
21
42
  export interface ChatMessage {
22
43
  id: string;
23
44
  role: 'user' | 'assistant' | 'system';
24
- content: string;
45
+ content: string | Array<{ type: string; [key: string]: any }>;
25
46
  toolInvocations?: ToolInvocation[];
26
47
  createdAt: number;
27
48
  /** Extended thinking / reasoning content */
@@ -38,6 +59,8 @@ export interface ChatMessage {
38
59
  isStreaming?: boolean;
39
60
  /** Cached TTS audio URL for read-aloud */
40
61
  audioUrl?: string;
62
+ /** Delegated agent identity */
63
+ agentInfo?: { id: string; name: string; avatar: string | null };
41
64
  }
42
65
 
43
66
  export interface ToolInvocation {
@@ -49,12 +72,6 @@ export interface ToolInvocation {
49
72
  toolCallId?: string;
50
73
  }
51
74
 
52
- export interface AliaChatSuggestion {
53
- label: string;
54
- icon?: string;
55
- prompt: string;
56
- }
57
-
58
75
  // ── Voice Types ──
59
76
 
60
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
- });