@alia.onl/sdk 3.1.1 → 3.1.3
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/alia.onl-sdk-3.1.3.tgz +0 -0
- package/package.json +80 -69
- package/src/animations.ts +6 -0
- package/src/components/AliaChatContent.tsx +215 -253
- package/src/components/AliaChatMessageList.tsx +9 -4
- package/src/components/AliaChatScreen.tsx +28 -42
- package/src/components/AliaChatSheet.tsx +40 -55
- package/src/components/AliaMark.tsx +144 -0
- package/src/components/AliaWelcomeMessage.tsx +5 -5
- package/src/components/Markdown.tsx +18 -18
- package/src/components/PlanPreviewCard.tsx +3 -2
- package/src/components/ThinkingIndicator.tsx +6 -27
- package/src/components/ui/chat-text-input.tsx +4 -3
- package/src/components/ui/dropdown-menu.web.tsx +9 -8
- package/src/components/ui/prompt-input/autocomplete.tsx +1 -1
- package/src/components/ui/prompt-input/context.tsx +1 -1
- package/src/components/ui/prompt-input/prompt-input.tsx +3 -1
- package/src/components/voice/AudioWaveVisualizer.tsx +27 -10
- package/src/components/voice/VoiceOverlay.tsx +37 -9
- package/src/hooks/useAliaChat.ts +6 -6
- package/src/hooks/useAmbientWave.ts +142 -0
- package/src/hooks/useAudioLevelMonitor.ts +6 -6
- package/src/hooks/useDocumentPicker.ts +1 -1
- package/src/hooks/useImagePicker.ts +1 -1
- package/src/hooks/useSoundEffects.ts +6 -5
- package/src/hooks/useSpeechToText.ts +21 -7
- package/src/hooks/useTTS.ts +6 -4
- package/src/hooks/useVoiceRoom.ts +52 -7
- package/src/index.ts +4 -2
- package/src/lib/content-utils.ts +18 -6
- package/src/lib/utils.ts +11 -0
- package/src/optional-deps.d.ts +76 -15
- package/src/types.ts +4 -4
- package/src/components/AliaFace.tsx +0 -452
|
@@ -12,11 +12,13 @@ import {
|
|
|
12
12
|
RoomEvent,
|
|
13
13
|
Track,
|
|
14
14
|
DisconnectReason,
|
|
15
|
+
type RemoteTrack,
|
|
15
16
|
type RemoteTrackPublication,
|
|
16
17
|
type RemoteParticipant,
|
|
17
18
|
type DataPublishOptions,
|
|
18
19
|
} from 'livekit-client';
|
|
19
20
|
import { useOxy } from '@oxyhq/services';
|
|
21
|
+
import { errorMessage } from '../lib/utils';
|
|
20
22
|
import type { RoomState, AgentState, VoiceMessage, VoiceToolInvocation } from '../types';
|
|
21
23
|
|
|
22
24
|
const API_URL = process.env.EXPO_PUBLIC_ALIA_API_URL ?? 'https://api.alia.onl';
|
|
@@ -63,7 +65,7 @@ interface CohostEnabledMsg { type: 'cohost.enabled' }
|
|
|
63
65
|
interface CohostDisabledMsg { type: 'cohost.disabled' }
|
|
64
66
|
interface CohostTurnMsg { type: 'cohost.turn_changed'; speaker: 'primary' | 'cohost' | 'user' }
|
|
65
67
|
interface CohostRoundMsg { type: 'cohost.round_complete'; turns: number }
|
|
66
|
-
interface ToolCallMsg { type: 'tool.call'; toolName: string; callId: string; args?:
|
|
68
|
+
interface ToolCallMsg { type: 'tool.call'; toolName: string; callId: string; args?: Record<string, unknown>; speaker: 'primary' | 'cohost' }
|
|
67
69
|
interface ToolResultMsg { type: 'tool.result'; callId: string; speaker: 'primary' | 'cohost' }
|
|
68
70
|
interface SessionEndedMsg { type: 'session.ended'; reason: string }
|
|
69
71
|
interface ErrorMsg { type: 'error'; code: string; message: string }
|
|
@@ -73,6 +75,31 @@ type AgentDataMessage =
|
|
|
73
75
|
| CohostEnabledMsg | CohostDisabledMsg | CohostTurnMsg | CohostRoundMsg
|
|
74
76
|
| ToolCallMsg | ToolResultMsg | SessionEndedMsg | ErrorMsg;
|
|
75
77
|
|
|
78
|
+
// ============== MIC DEVICE ERRORS ==============
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Map a microphone-acquisition failure to actionable copy. When
|
|
82
|
+
* `setMicrophoneEnabled(true)` calls getUserMedia and the device is missing,
|
|
83
|
+
* blocked, or busy, it surfaces a DOMException whose `.name` identifies the
|
|
84
|
+
* cause (legacy aliases included). Returns null for anything that isn't a known
|
|
85
|
+
* device error so the caller can fall back to generic handling.
|
|
86
|
+
*/
|
|
87
|
+
function micDeviceErrorMessage(name: string): string | null {
|
|
88
|
+
switch (name) {
|
|
89
|
+
case 'NotFoundError':
|
|
90
|
+
case 'DevicesNotFoundError':
|
|
91
|
+
return 'No microphone found — connect one and try again';
|
|
92
|
+
case 'NotAllowedError':
|
|
93
|
+
case 'PermissionDeniedError':
|
|
94
|
+
return 'Microphone access denied — allow it in your browser';
|
|
95
|
+
case 'NotReadableError':
|
|
96
|
+
case 'TrackStartError':
|
|
97
|
+
return 'Microphone is in use by another app';
|
|
98
|
+
default:
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
// ============== HOOK ==============
|
|
77
104
|
|
|
78
105
|
export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
|
|
@@ -324,13 +351,13 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
|
|
|
324
351
|
|
|
325
352
|
// Event: remote audio tracks (agent speaking)
|
|
326
353
|
// On web, must attach to a DOM element to play. On native, WebRTC plays audio automatically.
|
|
327
|
-
room.on(RoomEvent.TrackSubscribed, (track
|
|
354
|
+
room.on(RoomEvent.TrackSubscribed, (track: RemoteTrack) => {
|
|
328
355
|
if (track.kind === Track.Kind.Audio && hasDOM) {
|
|
329
356
|
track.attach();
|
|
330
357
|
}
|
|
331
358
|
});
|
|
332
359
|
|
|
333
|
-
room.on(RoomEvent.TrackUnsubscribed, (track) => {
|
|
360
|
+
room.on(RoomEvent.TrackUnsubscribed, (track: RemoteTrack) => {
|
|
334
361
|
if (hasDOM && track.kind === Track.Kind.Audio) {
|
|
335
362
|
track.detach();
|
|
336
363
|
}
|
|
@@ -351,15 +378,33 @@ export function useVoiceRoom(options: UseVoiceRoomOptions = {}) {
|
|
|
351
378
|
// Connect to the room
|
|
352
379
|
await room.connect(url, livekitToken, { autoSubscribe: true });
|
|
353
380
|
|
|
354
|
-
// Enable microphone — audio goes to LiveKit, agent picks it up server-side
|
|
355
|
-
|
|
381
|
+
// Enable microphone — audio goes to LiveKit, agent picks it up server-side.
|
|
382
|
+
// getUserMedia device failures (no mic, permission blocked, hardware busy)
|
|
383
|
+
// surface here; map them to actionable copy instead of a generic failure.
|
|
384
|
+
try {
|
|
385
|
+
await room.localParticipant.setMicrophoneEnabled(true);
|
|
386
|
+
} catch (micError: unknown) {
|
|
387
|
+
// DOMException is not `instanceof Error`, so read `.name` off the object.
|
|
388
|
+
const name =
|
|
389
|
+
typeof micError === 'object' && micError !== null && 'name' in micError &&
|
|
390
|
+
typeof micError.name === 'string'
|
|
391
|
+
? micError.name
|
|
392
|
+
: '';
|
|
393
|
+
const friendly = micDeviceErrorMessage(name);
|
|
394
|
+
if (!friendly) throw micError; // unknown failure — let the generic handler report it
|
|
395
|
+
console.error(`[useVoiceRoom] Microphone error (${name || 'unknown'}):`, micError);
|
|
396
|
+
setError(friendly);
|
|
397
|
+
setRoomState('error');
|
|
398
|
+
cleanup();
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
356
401
|
|
|
357
402
|
setRoomState('connected');
|
|
358
403
|
setAgentState('listening');
|
|
359
404
|
|
|
360
|
-
} catch (e:
|
|
405
|
+
} catch (e: unknown) {
|
|
361
406
|
console.error('[useVoiceRoom] Connection error:', e);
|
|
362
|
-
setError(e
|
|
407
|
+
setError(errorMessage(e, 'Failed to connect'));
|
|
363
408
|
setRoomState('error');
|
|
364
409
|
cleanup();
|
|
365
410
|
}
|
package/src/index.ts
CHANGED
|
@@ -9,8 +9,8 @@ export { AliaChatScreen } from './components/AliaChatScreen';
|
|
|
9
9
|
export type { AliaChatScreenProps } from './components/AliaChatScreen';
|
|
10
10
|
|
|
11
11
|
// ── Chat UI components ──
|
|
12
|
-
export {
|
|
13
|
-
export type {
|
|
12
|
+
export { AliaMark } from './components/AliaMark';
|
|
13
|
+
export type { AliaMarkProps, AliaMarkState } from './components/AliaMark';
|
|
14
14
|
export { ThinkingIndicator } from './components/ThinkingIndicator';
|
|
15
15
|
export { AliaMarkdown } from './components/Markdown';
|
|
16
16
|
export type { AliaMarkdownProps } from './components/Markdown';
|
|
@@ -36,6 +36,8 @@ export { useTTS } from './hooks/useTTS';
|
|
|
36
36
|
export type { UseTTSOptions } from './hooks/useTTS';
|
|
37
37
|
export { useSpeechToText, useSTTStore } from './hooks/useSpeechToText';
|
|
38
38
|
export type { UseSTTOptions } from './hooks/useSpeechToText';
|
|
39
|
+
export { useAmbientWave } from './hooks/useAmbientWave';
|
|
40
|
+
export type { UseAmbientWaveOptions, UseAmbientWaveResult, AmbientWaveVoice, AmbientWaveMode } from './hooks/useAmbientWave';
|
|
39
41
|
export { useSoundEffects, useVoiceSoundEffects } from './hooks/useSoundEffects';
|
|
40
42
|
export type { SoundName, SoundSources } from './hooks/useSoundEffects';
|
|
41
43
|
|
package/src/lib/content-utils.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
/** A single part of a multi-part chat message content array. */
|
|
2
|
+
export interface ContentPart {
|
|
3
|
+
type: string;
|
|
4
|
+
text?: string;
|
|
5
|
+
image_url?: { url?: string };
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type MessageContent = string | ContentPart[];
|
|
10
|
+
|
|
1
11
|
/**
|
|
2
12
|
* Extract text from message content, whether it's a string or multi-part array.
|
|
3
13
|
*/
|
|
4
|
-
export function getTextFromContent(content:
|
|
14
|
+
export function getTextFromContent(content: MessageContent | null | undefined): string {
|
|
5
15
|
if (typeof content === 'string') return content;
|
|
6
16
|
if (Array.isArray(content)) {
|
|
7
17
|
return content
|
|
8
|
-
.filter((p
|
|
9
|
-
.map((p
|
|
18
|
+
.filter((p) => p.type === 'text')
|
|
19
|
+
.map((p) => p.text || '')
|
|
10
20
|
.join('');
|
|
11
21
|
}
|
|
12
22
|
return String(content || '');
|
|
@@ -15,9 +25,11 @@ export function getTextFromContent(content: string | Array<{ type: string; [key:
|
|
|
15
25
|
/**
|
|
16
26
|
* Extract image URLs from multi-part message content.
|
|
17
27
|
*/
|
|
18
|
-
export function getImagesFromContent(content:
|
|
28
|
+
export function getImagesFromContent(content: MessageContent | null | undefined): string[] {
|
|
19
29
|
if (!Array.isArray(content)) return [];
|
|
20
30
|
return content
|
|
21
|
-
.filter((p:
|
|
22
|
-
|
|
31
|
+
.filter((p): p is ContentPart & { image_url: { url: string } } =>
|
|
32
|
+
p.type === 'image_url' && typeof p.image_url?.url === 'string',
|
|
33
|
+
)
|
|
34
|
+
.map((p) => p.image_url.url);
|
|
23
35
|
}
|
package/src/lib/utils.ts
CHANGED
|
@@ -5,6 +5,17 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
5
5
|
return twMerge(clsx(inputs));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/** Extract a human-readable message from an unknown caught error. */
|
|
9
|
+
export function errorMessage(err: unknown, fallback = 'Something went wrong'): string {
|
|
10
|
+
if (err instanceof Error) return err.message;
|
|
11
|
+
if (typeof err === 'object' && err !== null) {
|
|
12
|
+
const maybe = err as { message?: string };
|
|
13
|
+
if (maybe.message) return maybe.message;
|
|
14
|
+
}
|
|
15
|
+
if (typeof err === 'string') return err;
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
export function formatFileSize(bytes: number): string {
|
|
9
20
|
if (bytes === 0) return '0 B';
|
|
10
21
|
const k = 1024;
|
package/src/optional-deps.d.ts
CHANGED
|
@@ -10,22 +10,28 @@
|
|
|
10
10
|
// have their own real types — ambient declarations here would shadow them.
|
|
11
11
|
|
|
12
12
|
declare module 'livekit-client' {
|
|
13
|
+
export interface RemoteTrack {
|
|
14
|
+
kind?: string;
|
|
15
|
+
mediaStreamTrack?: MediaStreamTrack;
|
|
16
|
+
attach(): HTMLMediaElement;
|
|
17
|
+
detach(): HTMLMediaElement[];
|
|
18
|
+
}
|
|
13
19
|
export class Room {
|
|
14
|
-
on(event:
|
|
15
|
-
off(event:
|
|
20
|
+
on<Args extends unknown[]>(event: string, listener: (...args: Args) => void): this;
|
|
21
|
+
off<Args extends unknown[]>(event: string, listener: (...args: Args) => void): this;
|
|
16
22
|
localParticipant: LocalParticipant;
|
|
17
23
|
remoteParticipants: Map<string, RemoteParticipant>;
|
|
18
|
-
connect(url: string, token: string, options?:
|
|
24
|
+
connect(url: string, token: string, options?: Record<string, unknown>): Promise<void>;
|
|
19
25
|
disconnect(): Promise<void>;
|
|
20
26
|
state: string;
|
|
21
27
|
}
|
|
22
28
|
export class LocalParticipant {
|
|
23
29
|
publishData(data: Uint8Array, options?: DataPublishOptions): Promise<void>;
|
|
24
|
-
getTrackPublications(): Map<string,
|
|
25
|
-
getTrackPublication(source: string):
|
|
30
|
+
getTrackPublications(): Map<string, RemoteTrackPublication>;
|
|
31
|
+
getTrackPublication(source: string): RemoteTrackPublication | undefined;
|
|
26
32
|
setMicrophoneEnabled(enabled: boolean): Promise<void>;
|
|
27
|
-
audioTrackPublications: Map<string,
|
|
28
|
-
trackPublications: Map<string,
|
|
33
|
+
audioTrackPublications: Map<string, RemoteTrackPublication>;
|
|
34
|
+
trackPublications: Map<string, RemoteTrackPublication>;
|
|
29
35
|
}
|
|
30
36
|
export class RemoteParticipant {
|
|
31
37
|
getTrackPublications(): Map<string, RemoteTrackPublication>;
|
|
@@ -34,7 +40,7 @@ declare module 'livekit-client' {
|
|
|
34
40
|
identity: string;
|
|
35
41
|
}
|
|
36
42
|
export class RemoteTrackPublication {
|
|
37
|
-
track:
|
|
43
|
+
track: RemoteTrack | null;
|
|
38
44
|
source: string;
|
|
39
45
|
kind: string;
|
|
40
46
|
isSubscribed: boolean;
|
|
@@ -59,15 +65,54 @@ declare module 'livekit-client' {
|
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
declare module 'expo-haptics' {
|
|
69
|
+
export enum ImpactFeedbackStyle {
|
|
70
|
+
Light = 'light',
|
|
71
|
+
Medium = 'medium',
|
|
72
|
+
Heavy = 'heavy',
|
|
73
|
+
Soft = 'soft',
|
|
74
|
+
Rigid = 'rigid',
|
|
75
|
+
}
|
|
76
|
+
export function impactAsync(style?: ImpactFeedbackStyle): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
declare module 'expo-audio' {
|
|
63
|
-
export
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
export interface AudioPlaybackStatus {
|
|
81
|
+
didJustFinish?: boolean;
|
|
82
|
+
isLoaded?: boolean;
|
|
83
|
+
playing?: boolean;
|
|
84
|
+
}
|
|
85
|
+
export interface AudioPlayer {
|
|
86
|
+
addListener(event: 'playbackStatusUpdate', listener: (status: AudioPlaybackStatus) => void): { remove(): void };
|
|
87
|
+
play(): void;
|
|
88
|
+
pause(): void;
|
|
89
|
+
seekTo(seconds: number): void;
|
|
90
|
+
remove(): void;
|
|
91
|
+
release(): void;
|
|
92
|
+
}
|
|
93
|
+
/** A URI object, a bundled asset module id (number), or a required asset. */
|
|
94
|
+
export type AudioSource = { uri: string } | number;
|
|
95
|
+
export interface RecordingPreset {
|
|
96
|
+
isMeteringEnabled?: boolean;
|
|
97
|
+
[key: string]: unknown;
|
|
98
|
+
}
|
|
99
|
+
export interface AudioMode {
|
|
100
|
+
[key: string]: unknown;
|
|
101
|
+
}
|
|
102
|
+
export interface AudioRecorderState {
|
|
103
|
+
metering?: number;
|
|
104
|
+
isRecording?: boolean;
|
|
105
|
+
durationMillis?: number;
|
|
106
|
+
}
|
|
107
|
+
export function createAudioPlayer(source: AudioSource): AudioPlayer;
|
|
108
|
+
export function useAudioRecorder(preset: RecordingPreset): AudioRecorder;
|
|
109
|
+
export function useAudioRecorderState(recorder: AudioRecorder, interval: number): AudioRecorderState;
|
|
66
110
|
export function requestRecordingPermissionsAsync(): Promise<{ granted: boolean }>;
|
|
67
|
-
export function
|
|
68
|
-
export
|
|
111
|
+
export function getRecordingPermissionsAsync(): Promise<{ granted: boolean }>;
|
|
112
|
+
export function setAudioModeAsync(mode: AudioMode): Promise<void>;
|
|
113
|
+
export const RecordingPresets: { HIGH_QUALITY: RecordingPreset };
|
|
69
114
|
export class AudioRecorder {
|
|
70
|
-
constructor(preset:
|
|
115
|
+
constructor(preset: RecordingPreset);
|
|
71
116
|
prepareToRecordAsync(): Promise<void>;
|
|
72
117
|
record(): void;
|
|
73
118
|
stop(): Promise<void>;
|
|
@@ -81,6 +126,22 @@ declare module 'expo-clipboard' {
|
|
|
81
126
|
}
|
|
82
127
|
|
|
83
128
|
declare module 'expo-image-picker' {
|
|
84
|
-
export
|
|
129
|
+
export interface ImagePickerAsset {
|
|
130
|
+
uri: string;
|
|
131
|
+
fileName?: string | null;
|
|
132
|
+
fileSize?: number;
|
|
133
|
+
mimeType?: string;
|
|
134
|
+
}
|
|
135
|
+
export interface ImagePickerOptions {
|
|
136
|
+
allowsMultipleSelection?: boolean;
|
|
137
|
+
mediaTypes?: string;
|
|
138
|
+
aspect?: [number, number];
|
|
139
|
+
quality?: number;
|
|
140
|
+
}
|
|
141
|
+
export interface ImagePickerResult {
|
|
142
|
+
canceled: boolean;
|
|
143
|
+
assets: ImagePickerAsset[];
|
|
144
|
+
}
|
|
145
|
+
export function launchImageLibraryAsync(options?: ImagePickerOptions): Promise<ImagePickerResult>;
|
|
85
146
|
export const MediaTypeOptions: { Images: string; Videos: string; All: string };
|
|
86
147
|
}
|
package/src/types.ts
CHANGED
|
@@ -42,7 +42,7 @@ export interface PendingPlan {
|
|
|
42
42
|
export interface ChatMessage {
|
|
43
43
|
id: string;
|
|
44
44
|
role: 'user' | 'assistant' | 'system';
|
|
45
|
-
content: string | Array<{ type: string; [key: string]:
|
|
45
|
+
content: string | Array<{ type: string; [key: string]: unknown }>;
|
|
46
46
|
toolInvocations?: ToolInvocation[];
|
|
47
47
|
createdAt: number;
|
|
48
48
|
/** Extended thinking / reasoning content */
|
|
@@ -66,8 +66,8 @@ export interface ChatMessage {
|
|
|
66
66
|
export interface ToolInvocation {
|
|
67
67
|
toolName: string;
|
|
68
68
|
state: 'call' | 'partial-call' | 'result';
|
|
69
|
-
args?: Record<string,
|
|
70
|
-
result?:
|
|
69
|
+
args?: Record<string, unknown>;
|
|
70
|
+
result?: unknown;
|
|
71
71
|
/** OpenAI-format tool call ID for matching calls to results */
|
|
72
72
|
toolCallId?: string;
|
|
73
73
|
}
|
|
@@ -81,7 +81,7 @@ export interface VoiceToolInvocation {
|
|
|
81
81
|
toolCallId: string;
|
|
82
82
|
toolName: string;
|
|
83
83
|
state: 'call' | 'result';
|
|
84
|
-
args?:
|
|
84
|
+
args?: Record<string, unknown>;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export interface VoiceMessage {
|