@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.
- package/package.json +25 -3
- package/src/components/AliaChatContent.tsx +356 -0
- package/src/components/AliaChatInput.tsx +173 -26
- package/src/components/AliaChatMessageList.tsx +459 -168
- package/src/components/AliaChatScreen.tsx +140 -0
- package/src/components/AliaChatSheet.tsx +41 -44
- package/src/components/AliaWelcomeMessage.tsx +75 -0
- package/src/components/Markdown.tsx +58 -114
- package/src/components/PlanPreviewCard.tsx +76 -0
- package/src/components/ResearchProgressCard.tsx +159 -0
- package/src/components/ui/button.tsx +104 -0
- package/src/components/ui/chat-text-input.tsx +164 -0
- package/src/components/ui/dropdown-menu.tsx +2 -0
- package/src/components/ui/dropdown-menu.web.tsx +355 -0
- package/src/components/ui/prompt-input/actions.tsx +28 -0
- package/src/components/ui/prompt-input/add-menu.tsx +83 -0
- package/src/components/ui/prompt-input/attachments.tsx +183 -0
- package/src/components/ui/prompt-input/autocomplete.tsx +168 -0
- package/src/components/ui/prompt-input/context.tsx +71 -0
- package/src/components/ui/prompt-input/mic-button.tsx +53 -0
- package/src/components/ui/prompt-input/prompt-input.tsx +276 -0
- package/src/components/ui/prompt-input/submit-button.tsx +49 -0
- package/src/components/ui/prompt-input/textarea.tsx +64 -0
- package/src/components/ui/text.tsx +29 -0
- package/src/hooks/useAliaChat.ts +18 -8
- package/src/hooks/useDocumentPicker.ts +37 -0
- package/src/hooks/useImagePicker.ts +38 -0
- package/src/hooks/useSpeechToText.ts +25 -9
- package/src/index.ts +41 -3
- package/src/lib/content-utils.ts +23 -0
- package/src/lib/keyboard.native.tsx +6 -0
- package/src/lib/keyboard.tsx +25 -0
- package/src/lib/tool-registry.ts +55 -0
- package/src/lib/utils.ts +14 -0
- package/src/optional-deps.d.ts +17 -0
- package/src/types.ts +27 -10
- 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 };
|
package/src/hooks/useAliaChat.ts
CHANGED
|
@@ -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
|
|
435
|
+
const stop = useCallback(() => {
|
|
435
436
|
if (abortRef.current) {
|
|
436
437
|
abortRef.current.abort();
|
|
437
438
|
abortRef.current = null;
|
|
438
439
|
}
|
|
439
|
-
|
|
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'
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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,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 };
|
package/src/lib/utils.ts
ADDED
|
@@ -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
|
+
}
|
package/src/optional-deps.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
});
|