@alia.onl/sdk 10.1.1 → 11.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/CHANGELOG.md +67 -0
- package/README.md +24 -0
- package/package.json +4 -6
- package/src/components/AliaAvatar.tsx +66 -0
- package/src/components/AliaChatContent.tsx +236 -112
- package/src/components/AliaChatHeader.tsx +118 -0
- package/src/components/AliaChatMessageList.tsx +197 -260
- package/src/components/AliaChatScreen.tsx +23 -24
- package/src/components/AliaChatSheet.tsx +70 -316
- package/src/components/AliaWelcomeMessage.tsx +47 -50
- package/src/components/AmbientField.tsx +577 -0
- package/src/components/IdentityMark.tsx +10 -2
- package/src/components/Markdown.tsx +116 -40
- package/src/components/PlanPreviewCard.tsx +71 -55
- package/src/components/QuestionCard.tsx +62 -0
- package/src/components/ThinkingBlock.tsx +47 -0
- package/src/components/VoiceModeIcon.tsx +10 -0
- package/src/components/voice/VoiceControls.tsx +22 -73
- package/src/components/voice/VoiceSession.tsx +2 -1
- package/src/hooks/useAliaChat.ts +22 -15
- package/src/hooks/useAmbientWave.ts +35 -5
- package/src/hooks/useSoundEffects.ts +14 -11
- package/src/hooks/useSpeechToText.ts +9 -2
- package/src/hooks/useTTS.ts +163 -156
- package/src/hooks/useVoiceRoom.ts +228 -170
- package/src/index.ts +26 -6
- package/src/lib/catalogue.ts +17 -4
- package/src/lib/chat-stream.ts +25 -7
- package/src/lib/chat-transport.ts +2 -1
- package/src/lib/citations.ts +63 -0
- package/src/lib/content-utils.ts +3 -2
- package/src/lib/image-data-url.ts +15 -0
- package/src/lib/keyboard.tsx +1 -1
- package/src/lib/labels.tsx +81 -0
- package/src/lib/questions.ts +78 -0
- package/src/lib/speech-recognition.native.ts +15 -4
- package/src/lib/speech-recognition.ts +27 -7
- package/src/lib/task-labels.ts +67 -0
- package/src/lib/tool-call-status.ts +28 -0
- package/src/lib/tool-registry.ts +14 -14
- package/src/lib/voice-text.ts +12 -6
- package/src/lib/voice-turn.ts +4 -1
- package/src/lib/work-log-copy.ts +61 -0
- package/src/lib/work-log.ts +378 -0
- package/src/tools.ts +18 -0
- package/src/types.ts +5 -2
- package/src/voice.ts +12 -4
- package/src/components/Reasoning.tsx +0 -152
- package/src/components/ResearchProgressCard.tsx +0 -159
- package/src/components/ThinkingIndicator.tsx +0 -98
- package/src/components/ui/button.tsx +0 -104
- package/src/components/ui/chat-text-input.tsx +0 -184
- package/src/components/ui/dropdown-menu.tsx +0 -2
- package/src/components/ui/dropdown-menu.web.tsx +0 -383
- package/src/components/ui/prompt-input/actions.tsx +0 -28
- package/src/components/ui/prompt-input/add-menu.tsx +0 -83
- package/src/components/ui/prompt-input/attachments.tsx +0 -181
- package/src/components/ui/prompt-input/autocomplete.tsx +0 -168
- package/src/components/ui/prompt-input/context.tsx +0 -71
- package/src/components/ui/prompt-input/mic-button.tsx +0 -53
- package/src/components/ui/prompt-input/prompt-input.tsx +0 -279
- package/src/components/ui/prompt-input/submit-button.tsx +0 -50
- package/src/components/ui/prompt-input/textarea.tsx +0 -80
- package/src/components/ui/text.tsx +0 -29
- package/src/components/voice/AudioWaveVisualizer.tsx +0 -444
- package/src/components/voice/VoiceOverlay.tsx +0 -75
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bottom voice control bar that replaces the composer when voice mode is active.
|
|
3
|
-
* Contains status text and mute/end
|
|
3
|
+
* Contains status text and Bloom's mute/end call controls.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
6
|
+
import { CallControls } from '@oxy.so/bloom/call-ui';
|
|
7
|
+
import { Loading } from '@oxy.so/bloom/loading';
|
|
8
|
+
import { Text } from '@oxy.so/bloom/typography';
|
|
9
|
+
import { View } from 'react-native';
|
|
10
10
|
import type { RoomState, AgentState } from '../../types';
|
|
11
11
|
|
|
12
12
|
/** Every word the bar shows or announces. An app in another language passes its own. */
|
|
@@ -40,8 +40,6 @@ export interface VoiceControlsProps {
|
|
|
40
40
|
isMuted: boolean;
|
|
41
41
|
onToggleMute: () => void;
|
|
42
42
|
onEnd: () => void;
|
|
43
|
-
/** Override theme primary color (resolved hex/hsl value, not CSS var) */
|
|
44
|
-
primaryColor?: string;
|
|
45
43
|
/** Overrides for any of {@link VOICE_CONTROLS_LABELS}. */
|
|
46
44
|
labels?: Partial<VoiceControlsLabels>;
|
|
47
45
|
}
|
|
@@ -68,84 +66,35 @@ export function VoiceControls({
|
|
|
68
66
|
isMuted,
|
|
69
67
|
onToggleMute,
|
|
70
68
|
onEnd,
|
|
71
|
-
primaryColor,
|
|
72
69
|
labels: labelOverrides,
|
|
73
70
|
}: VoiceControlsProps) {
|
|
74
71
|
const labels = { ...VOICE_CONTROLS_LABELS, ...labelOverrides };
|
|
75
72
|
const statusText = getStatusText(roomState, agentState, isMuted, labels);
|
|
76
73
|
|
|
77
74
|
return (
|
|
78
|
-
<View
|
|
75
|
+
<View className="items-center gap-4 pb-6 pt-4">
|
|
79
76
|
{statusText ? (
|
|
80
|
-
<Text
|
|
77
|
+
<Text
|
|
78
|
+
className="text-lg font-medium text-text-primary"
|
|
79
|
+
accessibilityLiveRegion="polite"
|
|
80
|
+
aria-live="polite"
|
|
81
|
+
>
|
|
81
82
|
{statusText}
|
|
82
83
|
</Text>
|
|
83
84
|
) : null}
|
|
84
85
|
|
|
85
|
-
{roomState === 'connected'
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
style={[styles.button, isMuted ? { backgroundColor: '#ef4444' } : undefined]}
|
|
96
|
-
>
|
|
97
|
-
{isMuted ? (
|
|
98
|
-
<MicOff size={24} color="white" />
|
|
99
|
-
) : (
|
|
100
|
-
<Mic size={24} color="white" />
|
|
101
|
-
)}
|
|
102
|
-
</Pressable>
|
|
103
|
-
<Text className="text-xs text-muted-foreground">
|
|
104
|
-
{isMuted ? labels.unmute : labels.mute}
|
|
105
|
-
</Text>
|
|
106
|
-
</View>
|
|
107
|
-
|
|
108
|
-
<View style={styles.buttonWrapper}>
|
|
109
|
-
<Pressable
|
|
110
|
-
onPress={onEnd}
|
|
111
|
-
accessibilityRole="button"
|
|
112
|
-
accessibilityLabel={labels.end}
|
|
113
|
-
style={[styles.button, { backgroundColor: '#ef4444' }]}
|
|
114
|
-
>
|
|
115
|
-
<PhoneOff size={24} color="white" />
|
|
116
|
-
</Pressable>
|
|
117
|
-
<Text className="text-xs text-muted-foreground">{labels.end}</Text>
|
|
118
|
-
</View>
|
|
119
|
-
</View>
|
|
120
|
-
)}
|
|
86
|
+
{roomState === 'connected' ? (
|
|
87
|
+
<CallControls
|
|
88
|
+
muted={isMuted}
|
|
89
|
+
onMutedChange={onToggleMute}
|
|
90
|
+
onEndCall={onEnd}
|
|
91
|
+
// TODO(bloom): `CallControls` draws its captions for a dark call stage, so on the
|
|
92
|
+
// chat's light surface they vanish. The buttons keep their accessible names.
|
|
93
|
+
labels={{ mute: labels.mute, unmute: labels.unmute, endCall: labels.end }}
|
|
94
|
+
/>
|
|
95
|
+
) : null}
|
|
121
96
|
|
|
122
|
-
{roomState === 'connecting'
|
|
123
|
-
<ActivityIndicator size="large" className="text-muted-foreground" />
|
|
124
|
-
)}
|
|
97
|
+
{roomState === 'connecting' ? <Loading accessibilityLabel={labels.connecting} /> : null}
|
|
125
98
|
</View>
|
|
126
99
|
);
|
|
127
100
|
}
|
|
128
|
-
|
|
129
|
-
const styles = StyleSheet.create({
|
|
130
|
-
container: {
|
|
131
|
-
alignItems: 'center',
|
|
132
|
-
paddingBottom: 24,
|
|
133
|
-
paddingTop: 16,
|
|
134
|
-
},
|
|
135
|
-
buttonRow: {
|
|
136
|
-
flexDirection: 'row',
|
|
137
|
-
alignItems: 'center',
|
|
138
|
-
gap: 32,
|
|
139
|
-
},
|
|
140
|
-
buttonWrapper: {
|
|
141
|
-
alignItems: 'center',
|
|
142
|
-
gap: 8,
|
|
143
|
-
},
|
|
144
|
-
button: {
|
|
145
|
-
width: 56,
|
|
146
|
-
height: 56,
|
|
147
|
-
borderRadius: 28,
|
|
148
|
-
alignItems: 'center',
|
|
149
|
-
justifyContent: 'center',
|
|
150
|
-
},
|
|
151
|
-
});
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { useEffect, useRef } from 'react';
|
|
13
13
|
import { useVoiceRoom } from '../../hooks/useVoiceRoom';
|
|
14
|
+
import { VOICE_ERROR_MESSAGES } from '../../lib/speech-messages';
|
|
14
15
|
import { useAudioLevelMonitor } from '../../hooks/useAudioLevelMonitor';
|
|
15
16
|
import { useAudioLevels } from '../../hooks/useAudioLevels';
|
|
16
17
|
import { VoiceControls } from './VoiceControls';
|
|
@@ -57,7 +58,7 @@ export function VoiceSession({ apiUrl, onStateChange, onEnd }: VoiceSessionProps
|
|
|
57
58
|
useEffect(() => {
|
|
58
59
|
if (roomState === 'connected') reachedRoomRef.current = true;
|
|
59
60
|
if (error || roomState === 'error') {
|
|
60
|
-
onEnd();
|
|
61
|
+
onEnd(error ?? VOICE_ERROR_MESSAGES['turn-failed']);
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
64
|
if (roomState === 'disconnected' && reachedRoomRef.current) onEnd();
|
package/src/hooks/useAliaChat.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { useOxy } from '@oxy.so/services/ui/client';
|
|
3
3
|
import type { ChatMessage, ToolInvocation } from '../types';
|
|
4
|
-
import { getTextFromContent } from '../lib/content-utils';
|
|
4
|
+
import { getTextFromContent, type MessageContent } from '../lib/content-utils';
|
|
5
5
|
import { resolveModelId } from '../lib/catalogue';
|
|
6
6
|
import { streamAliaChat } from '../lib/chat-transport';
|
|
7
7
|
import type { AliaChatStreamEvent } from '../lib/chat-stream';
|
|
@@ -45,10 +45,15 @@ export interface UseAliaChatOptions {
|
|
|
45
45
|
accessToken?: string;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
export interface SendOptions {
|
|
49
|
+
images?: ReadonlyArray<string>;
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
export interface UseAliaChatReturn {
|
|
49
53
|
messages: ChatMessage[];
|
|
50
54
|
setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
|
|
51
|
-
|
|
55
|
+
/** `images` are `data:` or `https:` URLs sent with the turn as `image_url` parts. */
|
|
56
|
+
send: (text: string, options?: SendOptions) => void;
|
|
52
57
|
isStreaming: boolean;
|
|
53
58
|
stop: () => void;
|
|
54
59
|
clear: () => void;
|
|
@@ -112,12 +117,7 @@ function waitWithAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T>
|
|
|
112
117
|
* handler during its recorded compatibility window.
|
|
113
118
|
*/
|
|
114
119
|
export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn {
|
|
115
|
-
const {
|
|
116
|
-
apiUrl = API_URL,
|
|
117
|
-
model,
|
|
118
|
-
clientContext,
|
|
119
|
-
accessToken: accessTokenProp,
|
|
120
|
-
} = options;
|
|
120
|
+
const { apiUrl = API_URL, model, clientContext, accessToken: accessTokenProp } = options;
|
|
121
121
|
const requestUrl = '/v1/chat/completions';
|
|
122
122
|
|
|
123
123
|
const { oxyServices } = useOxy();
|
|
@@ -231,9 +231,10 @@ export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn
|
|
|
231
231
|
}, [cancelPendingTimer]);
|
|
232
232
|
|
|
233
233
|
const send = useCallback(
|
|
234
|
-
async (text: string) => {
|
|
234
|
+
async (text: string, options?: SendOptions) => {
|
|
235
235
|
const trimmed = text.trim();
|
|
236
|
-
|
|
236
|
+
const images = options?.images ?? [];
|
|
237
|
+
if (!trimmed && images.length === 0) return;
|
|
237
238
|
|
|
238
239
|
const activeToken = oxyServices.session.accessToken;
|
|
239
240
|
if (accessTokenProp !== undefined && accessTokenProp !== activeToken) {
|
|
@@ -253,10 +254,17 @@ export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn
|
|
|
253
254
|
flushPendingUpdates(previousPending);
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
const userContent: MessageContent =
|
|
258
|
+
images.length > 0
|
|
259
|
+
? [
|
|
260
|
+
...(trimmed ? [{ type: 'text', text: trimmed }] : []),
|
|
261
|
+
...images.map((url) => ({ type: 'image_url', image_url: { url } })),
|
|
262
|
+
]
|
|
263
|
+
: trimmed;
|
|
256
264
|
const userMessage: ChatMessage = {
|
|
257
265
|
id: generateId(),
|
|
258
266
|
role: 'user',
|
|
259
|
-
content:
|
|
267
|
+
content: userContent,
|
|
260
268
|
createdAt: Date.now(),
|
|
261
269
|
};
|
|
262
270
|
const assistantMessage: ChatMessage = {
|
|
@@ -281,7 +289,7 @@ export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn
|
|
|
281
289
|
setError(null);
|
|
282
290
|
setIsStreaming(true);
|
|
283
291
|
|
|
284
|
-
const apiMessages: Array<{ role: string; content:
|
|
292
|
+
const apiMessages: Array<{ role: string; content: MessageContent }> = [];
|
|
285
293
|
if (clientContext) apiMessages.push({ role: 'system', content: clientContext });
|
|
286
294
|
for (const message of history) {
|
|
287
295
|
if (message.role === 'system') continue;
|
|
@@ -295,7 +303,7 @@ export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn
|
|
|
295
303
|
}
|
|
296
304
|
}
|
|
297
305
|
}
|
|
298
|
-
apiMessages.push({ role: 'user', content:
|
|
306
|
+
apiMessages.push({ role: 'user', content: userContent });
|
|
299
307
|
|
|
300
308
|
const pending: PendingUpdates = {
|
|
301
309
|
assistantId: assistantMessage.id,
|
|
@@ -351,8 +359,7 @@ export function useAliaChat(options: UseAliaChatOptions = {}): UseAliaChatReturn
|
|
|
351
359
|
updateAssistant(pending.assistantId, (last) => ({
|
|
352
360
|
researchProgress: {
|
|
353
361
|
...event.progress,
|
|
354
|
-
subQuestions:
|
|
355
|
-
event.progress.subQuestions ?? last.researchProgress?.subQuestions,
|
|
362
|
+
subQuestions: event.progress.subQuestions ?? last.researchProgress?.subQuestions,
|
|
356
363
|
},
|
|
357
364
|
}));
|
|
358
365
|
return;
|
|
@@ -60,7 +60,12 @@ const ACTIVE_INTENSITY = 0.35;
|
|
|
60
60
|
* reanimated-web; the combiner is a pure-read `useDerivedValue`. Effect-free — no
|
|
61
61
|
* React `useEffect`.
|
|
62
62
|
*/
|
|
63
|
-
export function useAmbientWave({
|
|
63
|
+
export function useAmbientWave({
|
|
64
|
+
voice,
|
|
65
|
+
isTTSPlaying,
|
|
66
|
+
ttsWaveAmplitude,
|
|
67
|
+
isGenerating,
|
|
68
|
+
}: UseAmbientWaveOptions): UseAmbientWaveResult {
|
|
64
69
|
// ── Idle breath: subtle 0.04↔0.09 swell (~7s), started once on mount ──
|
|
65
70
|
const idleBreath = useSharedValue(0.04);
|
|
66
71
|
useAnimatedReaction(
|
|
@@ -131,14 +136,26 @@ export function useAmbientWave({ voice, isTTSPlaying, ttsWaveAmplitude, isGenera
|
|
|
131
136
|
// ── Combined amplitude — pure reads only (ticks on web) ──
|
|
132
137
|
const voiceAmplitude = voice?.waveAmplitude;
|
|
133
138
|
const waveAmplitude = useDerivedValue(
|
|
134
|
-
() =>
|
|
139
|
+
() =>
|
|
140
|
+
Math.max(
|
|
141
|
+
idleBreath.value,
|
|
142
|
+
voiceAmplitude ? voiceAmplitude.value : 0,
|
|
143
|
+
ttsWaveAmplitude.value,
|
|
144
|
+
stt.value,
|
|
145
|
+
thinkingAmp.value,
|
|
146
|
+
),
|
|
135
147
|
[voiceAmplitude, ttsWaveAmplitude],
|
|
136
148
|
);
|
|
137
149
|
|
|
138
150
|
// ── Mode → palette state + overlay intensity (plain derived) ──
|
|
139
151
|
return useMemo<UseAmbientWaveResult>(() => {
|
|
140
152
|
if (voice?.isActive && voice.isConnected) {
|
|
141
|
-
return {
|
|
153
|
+
return {
|
|
154
|
+
waveAmplitude,
|
|
155
|
+
agentState: voice.agentState,
|
|
156
|
+
intensity: ACTIVE_INTENSITY,
|
|
157
|
+
mode: 'voice',
|
|
158
|
+
};
|
|
142
159
|
}
|
|
143
160
|
if (isTTSPlaying) {
|
|
144
161
|
return { waveAmplitude, agentState: 'speaking', intensity: ACTIVE_INTENSITY, mode: 'tts' };
|
|
@@ -147,8 +164,21 @@ export function useAmbientWave({ voice, isTTSPlaying, ttsWaveAmplitude, isGenera
|
|
|
147
164
|
return { waveAmplitude, agentState: 'listening', intensity: ACTIVE_INTENSITY, mode: 'stt' };
|
|
148
165
|
}
|
|
149
166
|
if (isGenerating) {
|
|
150
|
-
return {
|
|
167
|
+
return {
|
|
168
|
+
waveAmplitude,
|
|
169
|
+
agentState: 'thinking',
|
|
170
|
+
intensity: THINKING_INTENSITY,
|
|
171
|
+
mode: 'thinking',
|
|
172
|
+
};
|
|
151
173
|
}
|
|
152
174
|
return { waveAmplitude, agentState: 'idle', intensity: IDLE_INTENSITY, mode: 'idle' };
|
|
153
|
-
}, [
|
|
175
|
+
}, [
|
|
176
|
+
waveAmplitude,
|
|
177
|
+
voice?.isActive,
|
|
178
|
+
voice?.isConnected,
|
|
179
|
+
voice?.agentState,
|
|
180
|
+
isTTSPlaying,
|
|
181
|
+
sttIsRecording,
|
|
182
|
+
isGenerating,
|
|
183
|
+
]);
|
|
154
184
|
}
|
|
@@ -70,18 +70,21 @@ export function useSoundEffects(enabled: boolean = false, sounds: SoundSources =
|
|
|
70
70
|
};
|
|
71
71
|
}, [enabled, sounds]);
|
|
72
72
|
|
|
73
|
-
const play = useCallback(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
const play = useCallback(
|
|
74
|
+
(sound: SoundName) => {
|
|
75
|
+
if (!enabled) return;
|
|
76
|
+
const player = playersRef.current.get(sound);
|
|
77
|
+
if (player) {
|
|
78
|
+
try {
|
|
79
|
+
player.seekTo(0);
|
|
80
|
+
player.play();
|
|
81
|
+
} catch {
|
|
82
|
+
// Playback failed — ignore
|
|
83
|
+
}
|
|
82
84
|
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
+
},
|
|
86
|
+
[enabled],
|
|
87
|
+
);
|
|
85
88
|
|
|
86
89
|
return { play };
|
|
87
90
|
}
|
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
} from '../lib/speech-recognition';
|
|
9
9
|
import type { SpeechRecognitionSession } from '../lib/speech-recognition-types';
|
|
10
10
|
import type { SpeechRecognizerChoice } from '../lib/speech-recognition-types';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
speechFailureCode,
|
|
13
|
+
VOICE_ERROR_MESSAGES,
|
|
14
|
+
type VoiceErrorCode,
|
|
15
|
+
} from '../lib/speech-messages';
|
|
12
16
|
import { defaultSpeechLanguage } from '../lib/speech-language';
|
|
13
17
|
|
|
14
18
|
// ============== OPTIONS ==============
|
|
@@ -81,7 +85,10 @@ export function useSpeechToText(options: UseSTTOptions = {}) {
|
|
|
81
85
|
const currentTextRef = useRef('');
|
|
82
86
|
const restartsRef = useRef(0);
|
|
83
87
|
/** The recognizer and tag this dictation uses, chosen once when it starts. */
|
|
84
|
-
const recognizerRef = useRef<{ lang: string; service?: string; silent: boolean }>({
|
|
88
|
+
const recognizerRef = useRef<{ lang: string; service?: string; silent: boolean }>({
|
|
89
|
+
lang,
|
|
90
|
+
silent: false,
|
|
91
|
+
});
|
|
85
92
|
const endWaitersRef = useRef<Array<() => void>>([]);
|
|
86
93
|
const lastMeteringRef = useRef(0);
|
|
87
94
|
const mountedRef = useRef(true);
|