@alia.onl/sdk 13.1.0 → 15.2.1

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 (55) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +83 -20
  3. package/package.json +8 -4
  4. package/src/components/AliaAvatarIcon.tsx +30 -0
  5. package/src/components/AliaChatContent.tsx +57 -107
  6. package/src/components/AliaChatMessageList.tsx +2 -10
  7. package/src/components/AliaChatScreen.tsx +28 -25
  8. package/src/components/AliaChatSheet.tsx +30 -8
  9. package/src/components/AliaComposer.tsx +382 -0
  10. package/src/components/ComposerDropZone.tsx +235 -0
  11. package/src/components/voice/VoiceControls.tsx +28 -4
  12. package/src/components/voice/VoiceSession.tsx +23 -7
  13. package/src/hooks/useAliaActivity.ts +31 -0
  14. package/src/hooks/useAliaChat.ts +14 -3
  15. package/src/hooks/useAmbientWave.ts +3 -3
  16. package/src/hooks/useAttachmentIntake.ts +401 -0
  17. package/src/hooks/useAudioLevelMonitor.ts +2 -2
  18. package/src/hooks/useDictation.ts +295 -0
  19. package/src/hooks/useFocusWhenPresented.ts +41 -0
  20. package/src/hooks/usePendingQuestions.ts +19 -0
  21. package/src/hooks/useTTS.ts +24 -63
  22. package/src/hooks/useVoiceRoom.ts +148 -671
  23. package/src/index.ts +19 -3
  24. package/src/lib/activity-socket.ts +102 -0
  25. package/src/lib/activity-store.ts +53 -0
  26. package/src/lib/api-url.ts +2 -0
  27. package/src/lib/attachment-intake.ts +154 -0
  28. package/src/lib/attachment-tiles.ts +74 -0
  29. package/src/lib/audio-level.ts +2 -2
  30. package/src/lib/dictation-recorder-types.ts +37 -0
  31. package/src/lib/dictation-recorder.native.ts +90 -0
  32. package/src/lib/dictation-recorder.ts +131 -0
  33. package/src/lib/dictation.ts +208 -0
  34. package/src/lib/labels.tsx +50 -1
  35. package/src/lib/pcm.ts +57 -0
  36. package/src/lib/speech-language.ts +1 -1
  37. package/src/lib/speech-messages.ts +43 -65
  38. package/src/lib/speech-synthesis.ts +104 -9
  39. package/src/lib/turn-state.ts +15 -0
  40. package/src/lib/voice-audio-engine-types.ts +59 -0
  41. package/src/lib/voice-audio-engine.native.ts +232 -0
  42. package/src/lib/voice-audio-engine.ts +350 -0
  43. package/src/lib/voice-call.ts +539 -0
  44. package/src/lib/voice-levels.ts +7 -3
  45. package/src/lib/voice-protocol.ts +309 -0
  46. package/src/tools.ts +1 -0
  47. package/src/types.ts +63 -4
  48. package/src/voice.ts +31 -10
  49. package/tsconfig.json +2 -0
  50. package/src/hooks/useSpeechToText.ts +0 -268
  51. package/src/lib/speech-recognition-types.ts +0 -108
  52. package/src/lib/speech-recognition.native.ts +0 -237
  53. package/src/lib/speech-recognition.ts +0 -308
  54. package/src/lib/voice-text.ts +0 -198
  55. package/src/lib/voice-turn.ts +0 -89
package/CHANGELOG.md CHANGED
@@ -1,5 +1,195 @@
1
1
  # @alia.onl/sdk
2
2
 
3
+ ## 15.2.1
4
+
5
+ - **The shipped source compiles under `noUnusedLocals`.** The package ships its `.tsx` source, so a
6
+ consumer's own `tsc` (an app with `noUnusedLocals`/`noUnusedParameters`, such as the Oxy website)
7
+ type-checks it and failed on eight unused imports in `AliaChatMessageList` (`Pressable` and the
8
+ reanimated worklet helpers, left behind by the work-log move). They are gone, and the SDK's
9
+ tsconfig now turns the two flags on so it cannot regress.
10
+
11
+ ## 15.2.0
12
+
13
+ - **`AliaChatSheetRef.present(turn?)`.** Given a turn (`{ text, images? }`), the conversation opens
14
+ over the sheet with that message already sent: the "ask Alia about this" button of a profile, a
15
+ post or a file (the way X opens a conversation with Grok about a post). Without a turn nothing
16
+ changes. `AliaTurn` is exported.
17
+
18
+ ## 15.0.0
19
+
20
+ A voice call is a realtime speech-to-speech session through Alia and Kaana, and the composer's
21
+ microphone is transcribed by a Kaana model (Alia ADR 0017). Nothing is recognized by the browser
22
+ or the operating system any more, anywhere in the SDK.
23
+
24
+ - **Breaking: `useVoiceRoom` is a call over Alia's `GET /v1/voice/session` WebSocket.** It sends
25
+ the microphone as PCM16 24 kHz frames and plays the answer's PCM16 frames; turn detection and
26
+ barge-in are the model's, on the server, and tools, memory and the stored transcript are Alia's.
27
+ New options: `conversationId` (where the transcript is stored; omitted, each call starts a
28
+ conversation of its own), `agentId`, `voicePreference`, `lang`, `onMessagesSaved` (refresh the
29
+ thread), `onApprovalRequested`, and the test seams `createAudioEngine` / `openSocket`. Gone:
30
+ `sendTurn`, `chatModel`, `model`, `endOfUtteranceMs`, `bargeIn`, `synthesisError`, `turnError`,
31
+ `turnErrorCode`, `warning`, `warningCode`.
32
+ - **Honest states.** `callState` (`VoiceCallState`) is `connecting` → `listening` (only once the
33
+ session is ready upstream, the microphone delivered a buffer AND Alia acknowledged the first
34
+ appended audio) → `user-speaking` → `thinking` → `using-tool` → `speaking` (only when the first
35
+ answer frame is PLAYING) → … → `ended { reason }` or `error { code }`, plus `reconnecting`
36
+ while a dropped socket resumes within the session's resume window or Alia re-opens the session
37
+ upstream. `roomState` (now with `reconnecting`) and `agentState` are its coarse projections;
38
+ `endedReason` says why a call ended — `call-moved-elsewhere` (the person opened a call
39
+ elsewhere) is a reason, not an error. `endingSoonMs`, `model` (the serving model's name; never
40
+ the operator) are new.
41
+ - **Barge-in:** when the server hears the person start speaking, the playback queue is flushed and
42
+ Alia is told how many milliseconds of the answer were actually heard, so the model remembers
43
+ what was said, not what it generated.
44
+ - **The audio engine** (`createVoiceAudioEngine`, `VoiceAudioEngine`): web is `getUserMedia` with
45
+ echo cancellation, noise suppression and gain control plus two `AudioWorklet`s (capture to
46
+ 24 kHz PCM16, jitter-buffered playback) in one `AudioContext` — Firefox gets calls. iOS and
47
+ Android use **`react-native-audio-api`** (new optional peer, `^0.13.6`), capture and playback in
48
+ one native stack; iOS runs `playAndRecord` in mode `voiceChat` (voice processing). **A host app
49
+ needs a new native build** with the library's config plugin. On Android the library exposes no
50
+ `voiceCommunication` input preset yet, so platform echo cancellation is a gate to measure on
51
+ device (ADR 0017 §4).
52
+ - **Breaking: `useSpeechToText` and `useSTTStore` are replaced by `useDictation` and
53
+ `useDictationStore`.** Dictation records a clip (`MediaRecorder` on web, Opus/WebM or MP4 on
54
+ Safari; the `expo-audio` recorder on iOS/Android, m4a sent as `audio/mp4`) and transcribes it with
55
+ `POST /v1/audio/transcriptions` when the person stops. States: `idle`, `requesting-permission`,
56
+ `recording` (only once the recorder started; levels are the recorder's), `transcribing`,
57
+ `error`. The text arrives through `onText`, never partially; a failed transcription keeps the
58
+ clip for one `retry()`. Clips stop by themselves at five minutes, and `remainingMs` counts the
59
+ last 30 s. `requestTranscription` and `DictationError` are exported. Dictation works in Firefox
60
+ now.
61
+ - **`AliaComposer`** dictates with `useDictation`: the transcription is appended to the draft the
62
+ person has when it arrives, a failure is said with a Retry that re-sends the same clip, and the
63
+ time left shows over the composer in the last 30 s. New prop `apiUrl`; new label
64
+ `dictationRemaining`.
65
+ - **Breaking: `VoiceErrorCode` is rewritten.** The recognizer's codes (`speech-*`,
66
+ `microphone-*`, `recognizer-*`, `no-audio-input`, `turn-failed`, `no-answer`, `not-played`,
67
+ `synthesis-*`) are gone. It is now Alia's call codes (`not-authenticated`,
68
+ `credential-not-supported`, `plan-required`, `credits-exhausted`, `agent-unavailable`,
69
+ `invalid-request`, `resume-unavailable`, `realtime-unavailable`, `realtime-limited`,
70
+ `realtime-rejected`, `realtime-failed`), the device's (`mic-denied`, `mic-unavailable`,
71
+ `connection-lost`, `voice-unavailable`) and dictation's (`too-long`,
72
+ `transcription-unavailable`, `transcription-limited`, `transcription-rejected`,
73
+ `transcription-failed`). An app with its own translations replaces its keys.
74
+ - **Removed:** `createAliaVoiceTurnSender` and the `VoiceTurn*` types, the device recognizers
75
+ (`speech-recognition*`, `chooseSpeechRecognizer`), `isInterruption` and the rest of
76
+ `voice-text`, and the **`expo-speech-recognition` peer** — a host app removes it and its config
77
+ plugin. Read-aloud (`useTTS`, `/v1/audio/speech`) is unchanged.
78
+ - `VoiceSession` takes `conversationId`, `agentId`, `locale` and `voice`; `VoiceControls` takes
79
+ `callState` and shows `reconnecting` and `usingTool` (new labels).
80
+ - The `./voice` entry still holds the call: `scripts/check-entry-isolation.mjs` now also proves the
81
+ root entry never reaches `react-native-audio-api` (nor `expo-speech-recognition`).
82
+
83
+ ## 14.1.0
84
+
85
+ An "Ask Alia" button can show her live avatar and react to what she is doing.
86
+
87
+ - **`AliaAvatarIcon`**: a Bloom icon component (`icon={AliaAvatarIcon}`) that draws the live avatar
88
+ at the icon size under its own `AgentAvatarProvider` on `ALIA_CHARACTER_RUNTIME_URL`, not interactive.
89
+ - **`useAliaActivity()`** returns `'idle' | 'thinking' | 'working' | 'writing'`, the busiest of the
90
+ turns running in this app (`useAliaChat` now publishes its lifecycle to a shared store) and the
91
+ server's `alia:activity` events for the signed-in person (one shared `socket.io-client` connection
92
+ per API origin, none while signed out, closed with the last consumer). New dependency:
93
+ `socket.io-client`. Needs the API that emits `alia:activity` (an older API simply never sends it:
94
+ buttons then follow local turns only).
95
+ - The header mark and the buttons read a turn from one function (`turnActivityState`).
96
+
97
+ ## 14.0.0
98
+
99
+ Read-aloud asks the server every time, and the server answers a clip it already made from storage:
100
+ a message read aloud twice, or a sentence voice mode says again, is synthesized and paid for once.
101
+
102
+ - **Breaking: `useTTS().readAloud(messageId, text, conversationId?)`** loses its fourth argument,
103
+ `audioUrl`. A message no longer carries a stored clip of its own: `POST /v1/audio/speech` finds
104
+ the clip by its text, voice, speed and speech model, for the signed-in person, and answers a
105
+ stored one at once. There is no "play the stored link, remake it if it has gone" path any more.
106
+ - **Breaking: `ChatMessage.audioUrl` is removed.** `GET /conversations/:id` no longer sends it.
107
+ - `requestSpeechClip`'s `conversationId` + `messageId` now file the clip under that conversation,
108
+ so deleting or clearing the conversation deletes it. The route still answers 404 for a message
109
+ the person does not hold, and `requestId` is present only when the clip was made by that request.
110
+
111
+ A voice call says "Listening…" only while something listens.
112
+
113
+ - **`useVoiceRoom` stays `connecting` until the recognizer has started** (`roomState` is
114
+ `'connected'` and `agentState` `'listening'` from the first session's start, not from
115
+ `connect()`), and a session that does not start within 8 s ends the call as
116
+ `recognizer-not-started`.
117
+ - **An `aborted` nobody asked for is a failure.** Chrome runs one microphone recognition per
118
+ browser and a new one aborts the live one, in any tab; the engines now report that as the
119
+ failure code `interrupted` (only the abort the caller asked for stays silent), and a call cut
120
+ off three times in a row ends as `recognizer-aborted` instead of restarting forever. The web
121
+ engine serializes its own sessions, so a page never aborts itself.
122
+ - **The call watches itself while it listens:** voice-level sound for 5 s with no word back ends
123
+ it as `recognizer-unresponsive`; five sessions in a row that end without listening end it with
124
+ their error's code; a microphone silent for 10 s raises **`warning` / `warningCode`**
125
+ (`no-audio-input`) without ending the call. Recognizer errors are logged as a structured
126
+ `console.warn`, never with the transcript.
127
+ - **`VoiceErrorCode` gains** `recognizer-not-started`, `recognizer-unresponsive`,
128
+ `recognizer-aborted` and `no-audio-input` (an app with its own translations adds them);
129
+ `VoiceControls` takes a `notice` it shows in place of "Listening…". Dictation reports an
130
+ interrupted session as `recognizer-aborted` too.
131
+
132
+ ## 13.1.1
133
+
134
+ - **The avatar flights load the runtime too.** 13.1.0 mounted the character runtime provider inside
135
+ the chat, but the sheet's avatar-flight layer (the avatar that flies from the welcome into the
136
+ header) is drawn outside it, so it asked the host for its own `/bloom-character/runtime.mjs` and got
137
+ the app's HTML back ("Expected a JavaScript-or-Wasm module script but the server responded with
138
+ a MIME type of text/html"). `AliaChatSheet` and `AliaChatScreen` now wrap everything they draw in the
139
+ provider, once; `AliaChatContent` no longer mounts one (it is not an entry point on its own).
140
+
141
+ ## 13.2.0
142
+
143
+ One composer for the sheet, the screen and the Alia app: the app's composer moved here and the
144
+ app consumes it.
145
+
146
+ - **`AliaComposer`** (exported) is Bloom's `ComposerPanel` plus what every Alia surface needs the
147
+ same way: files by paste and drop (read with real progress, retried, refused with a sentence),
148
+ dictation, the suggestions over the field and the questions Alia asked, docked above it. What
149
+ differs per surface is a prop: `addMenu` / `onAddMenuSelect`, `modes` / `mode` /
150
+ `onModeChange` (a power-level selector), `attachments` / `onAddAttachment` /
151
+ `onRemoveAttachment` (a composer given no `onAddAttachment` takes no paste or drop),
152
+ `attachmentKinds` (`['image']` refuses a document with a sentence), `suggestions`, `questions`,
153
+ `status`, `emptyAction`, `accessory`, `onKeyPress`, `speechLanguage`, `labels`, `onError`.
154
+ `AliaChatContent` (so the sheet and the screen) renders it with photos, voice and suggestions;
155
+ a host gets today's behaviour with no setup.
156
+ - **The sheet's composer is the panel, not the pill.** The pill has no mode selector, status tab
157
+ or question slot, which the Alia app needs; the sheet and the app now draw the same card. The
158
+ agent-working light (`ComposerLoader`) is not drawn around it, as in the app: a turn in flight
159
+ is its stop control and the thread's thinking line.
160
+ - **Photos are `AliaAttachment`s** (exported type): the chat's picked photos wait as the same
161
+ tiles a dropped or pasted image becomes, with the same remove and error states.
162
+ - **`AliaChatLabels` gains** `composerMessage`, `composerAddMenu`, `composerModes`,
163
+ `composerVoice`, `composerSend`, `composerStop`, `composerRemove`, `composerRetry`,
164
+ `composerUntitled`, `fileReadFailed`, `fileTooLarge`, `fileEmpty`, `fileUnsupported`,
165
+ `dropHint`, `dropUnavailable` and `voiceError(code)`, all with English defaults. A nested
166
+ `AliaChatLabelsProvider` now refines the labels already in reach instead of resetting them.
167
+ - **`usePendingQuestions(messages, isStreaming)`** (exported) is the "asked above the composer,
168
+ put away until she asks again" rule both the chat and the app used to keep a copy of.
169
+ - **`classifyIntake` / `MAX_ATTACHMENT_BYTES`** (from `@alia.onl/sdk/tools`) are the rules a file
170
+ passes to be attached.
171
+ - **The sheet focuses its composer once it has presented.** `AliaChatSheet` hands each sheet's
172
+ `animatedProgress` (Bloom's own "fully presented" signal, no timer) to its chat, whose composer
173
+ takes focus when it reads `1`: the person can type at once and the keyboard opens on native. It
174
+ focuses once per `present()` for the main sheet and for the conversation sheet over it; dragging
175
+ a sheet part-way and letting go does not take the focus again. `AliaChatContent` takes
176
+ `presentedProgress` for a host that builds its own sheet; the screen and the app's own screens
177
+ pass none and keep their focus. Not verified on a device.
178
+
179
+ A voice call that cannot speak ends and says why, instead of listening on in silence:
180
+
181
+ - **`requestSpeechClip` throws a `SpeechClipError`** (exported) with the route's HTTP `status`,
182
+ its typed `code` (`SPEECH_UNAVAILABLE`, `SPEECH_LIMIT_REACHED`, `SPEECH_REJECTED`,
183
+ `SPEECH_PROVIDER_FAILED`, `SPEECH_GENERATION_FAILED`, `INVALID_REQUEST`, `NOT_AUTHENTICATED`, or
184
+ `null`), `retryable` and `requestId`. An empty token is `NOT_AUTHENTICATED` without a request.
185
+ - **`useVoiceRoom` ends the call when a clip cannot be made or played**, after the answer has
186
+ finished streaming into the transcript, with `errorCode` `synthesis-unavailable`,
187
+ `synthesis-limited`, `synthesis-rejected`, `synthesis-failed`, `not-authenticated` (no session
188
+ token) or `not-played`; `synthesisError` is the `SpeechClipError` behind it. A turn no longer
189
+ skips a sentence it could not speak, and `turnErrorCode` is no longer `not-played`.
190
+ - `VoiceErrorCode` gains the four `synthesis-*` codes; an app that translates every code needs
191
+ their sentences.
192
+
3
193
  ## 13.1.0
4
194
 
5
195
  The chat works in any app without host setup it did not document in code:
package/README.md CHANGED
@@ -53,31 +53,39 @@ Two entry points:
53
53
 
54
54
  ## Speech
55
55
 
56
- Dictation (`useSpeechToText`) and the voice call (`useVoiceRoom`) recognize
57
- speech **on the device**: the Web Speech API in browsers that have it (Chromium
58
- and Safari — not Firefox, where `useSpeechToText().isSupported` is `false`), and
59
- [`expo-speech-recognition`](https://github.com/jamsch/expo-speech-recognition)
60
- on iOS and Android. No audio is sent to Alia; whether the platform's recognizer
61
- uses a network service of its own is the platform's (Chrome's does).
62
-
63
- On native, add `expo-speech-recognition` (a peer dependency, like `expo-audio`:
64
- the root entry's mic button reaches it) and its config plugin to your app, then
65
- make a new native build:
56
+ Nothing is recognized on the device. Speech is recognized by Kaana, through
57
+ Alia (Alia ADR 0017):
58
+
59
+ - **Dictation** (`useDictation`, the composer's mic) records a clip — the
60
+ browser's `MediaRecorder`, or the `expo-audio` recorder on iOS and Android —
61
+ and, when the person stops, sends it to `POST /v1/audio/transcriptions`.
62
+ The text arrives once, never partially. Five minutes per clip. Free on every
63
+ Alia plan.
64
+ - **A voice call** (`useVoiceRoom`, `VoiceSession`) is a realtime
65
+ speech-to-speech session: a WebSocket to Alia's `GET /v1/voice/session`
66
+ carrying PCM16 24 kHz audio both ways. Turn detection and barge-in are the
67
+ model's; tools run in Alia; the transcript is stored in the conversation.
68
+ Voice calls need a paid Alia plan and use credits per minute.
69
+
70
+ Audio reaches Alia, Kaana and the model's provider; Alia logs no audio and no
71
+ transcript content.
72
+
73
+ On iOS and Android a call needs
74
+ [`react-native-audio-api`](https://github.com/software-mansion/react-native-audio-api)
75
+ (an optional peer of `@alia.onl/sdk/voice`; the root entry never reaches it)
76
+ and its config plugin, then a new native build:
66
77
 
67
78
  ```json
68
- ["expo-speech-recognition", {
69
- "microphonePermission": "Allow $(PRODUCT_NAME) to hear what you dictate and say in voice calls.",
70
- "speechRecognitionPermission": "Allow $(PRODUCT_NAME) to turn your speech into text."
79
+ ["react-native-audio-api", {
80
+ "iosMicrophonePermission": "Allow $(PRODUCT_NAME) to hear what you dictate and say in voice calls.",
81
+ "iosBackgroundMode": false,
82
+ "androidForegroundService": false,
83
+ "androidPermissions": ["android.permission.RECORD_AUDIO"]
71
84
  }]
72
85
  ```
73
86
 
74
- A voice call is a turn loop, not a realtime session: it listens until the
75
- person pauses, sends what they said through the ordinary chat path
76
- (`POST /v1/chat/completions` with `responseMode: "voice"`, or your own
77
- `sendTurn`), and speaks the answer sentence by sentence through
78
- `POST /v1/audio/speech` in the product voice (`male` / `female`). Talking over
79
- the answer interrupts it. Pass `lang` to both hooks to follow your app's
80
- language.
87
+ Dictation needs only `expo-audio` (already a peer). Pass `lang` to both hooks
88
+ (`speechLanguage` to `AliaComposer`) to follow your app's language.
81
89
 
82
90
  ## Use
83
91
 
@@ -112,6 +120,61 @@ components render. Those events are the product runtime's, not part of the Oxy
112
120
  generic inference contract, and this package is written against them on
113
121
  purpose: it is a **product** client.
114
122
 
123
+ ## An "Ask Alia" button with her live avatar
124
+
125
+ ```tsx
126
+ import { AliaAvatarIcon, useAliaActivity } from '@alia.onl/sdk';
127
+
128
+ <Button icon={AliaAvatarIcon} onPress={openAlia}>Ask Alia</Button>;
129
+ const state = useAliaActivity(); // 'idle' | 'thinking' | 'working' | 'writing'
130
+ ```
131
+
132
+ `AliaAvatarIcon` is a Bloom icon component (`BloomIconComponent`): pass it as `icon` /
133
+ `leadingIcon`. It draws the live 3D avatar at the icon size under its own
134
+ `AgentAvatarProvider` (`ALIA_CHARACTER_RUNTIME_URL`), and plays what Alia is doing.
135
+
136
+ `useAliaActivity()` is the busiest of two sources, so a button reacts even with the sheet closed:
137
+
138
+ - a turn running in this app: every `useAliaChat` publishes its lifecycle to a shared store;
139
+ - her runs for the signed-in person elsewhere (background work, another device): the API's
140
+ `alia:activity` event on the person's `user:<id>` room, over ONE Socket.IO connection per
141
+ API origin, opened with the Oxy session's token only while someone is signed in and a
142
+ consumer is mounted, closed with the last one. A run that goes quiet for 90 s is treated as over.
143
+
144
+ `socket.io-client` is a regular dependency, not a peer: the SDK owns this connection and an
145
+ app should not have to install a transport to use a button.
146
+
147
+ ## The composer
148
+
149
+ The sheet, the screen and the Alia app share one composer, `AliaComposer`: Bloom's
150
+ `ComposerPanel` with files by paste and drop (read with real progress, retried, refused with a
151
+ sentence), dictation, suggestions over the field and Alia's questions docked above it.
152
+ `AliaChatSheet` and `AliaChatScreen` render it with photos, voice and suggestions, and
153
+ `AliaChatSheet` focuses it once the sheet has presented. To build your own surface, mount it and
154
+ pass what the surface has:
155
+
156
+ ```tsx
157
+ import { AliaComposer } from '@alia.onl/sdk';
158
+
159
+ <AliaComposer
160
+ value={draft}
161
+ onValueChange={setDraft}
162
+ onSubmit={send}
163
+ busy={isStreaming}
164
+ onStop={stop}
165
+ modes={powerLevels} // the mode selector; omit to hide it
166
+ mode={level}
167
+ onModeChange={setLevel}
168
+ addMenu={groups} // the add menu's rows, and what pressing one does
169
+ onAddMenuSelect={onAdd}
170
+ attachments={files} // omit `onAddAttachment` and the composer takes no paste or drop
171
+ onAddAttachment={addFile}
172
+ onRemoveAttachment={removeFile}
173
+ attachmentKinds={['image']} // what your send carries; others are refused with a sentence
174
+ labels={{ composerSend: 'Enviar' }} // anything omitted is English
175
+ />;
176
+ ```
177
+
115
178
  ## Which endpoint the SDK calls, and why
116
179
 
117
180
  The hook sends its turns to `POST {apiUrl}/v1/chat/completions`, with `apiUrl`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alia.onl/sdk",
3
- "version": "13.1.0",
4
- "description": "Alia AI chat SDK — reusable chat UI, voice, and streaming components for Oxy apps",
3
+ "version": "15.2.1",
4
+ "description": "Alia AI chat SDK — reusable chat UI, voice calls, dictation and streaming components for Oxy apps",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
7
7
  "source": "src/index.ts",
@@ -32,6 +32,7 @@
32
32
  "dependencies": {
33
33
  "clsx": "^2.1.1",
34
34
  "react-native-markdown-display": "^7.0.2",
35
+ "socket.io-client": "^4.8.3",
35
36
  "tailwind-merge": "^3.7.0",
36
37
  "zustand": "^5.0.15"
37
38
  },
@@ -57,7 +58,7 @@
57
58
  "nativewind": "^5.0.0-preview.3",
58
59
  "expo-clipboard": "^57.0.0",
59
60
  "expo-file-system": "^57.0.0",
60
- "expo-speech-recognition": "^57.1.0"
61
+ "react-native-audio-api": "^0.13.6"
61
62
  },
62
63
  "peerDependenciesMeta": {
63
64
  "nativewind": {
@@ -65,6 +66,9 @@
65
66
  },
66
67
  "expo-clipboard": {
67
68
  "optional": true
69
+ },
70
+ "react-native-audio-api": {
71
+ "optional": true
68
72
  }
69
73
  },
70
74
  "devDependencies": {
@@ -72,6 +76,6 @@
72
76
  "react-test-renderer": "19.2.3",
73
77
  "typescript": "^5.9.3",
74
78
  "vitest": "^4.1.10",
75
- "expo-speech-recognition": "~57.1.0"
79
+ "react-native-audio-api": "0.13.6"
76
80
  }
77
81
  }
@@ -0,0 +1,30 @@
1
+ import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
2
+ import type { BloomIconComponent } from '@oxy.so/bloom/icons';
3
+ import { useAliaActivity } from '../hooks/useAliaActivity';
4
+ import { ALIA_CHARACTER_RUNTIME_URL } from '../lib/character-runtime';
5
+ import { AliaAvatar } from './AliaAvatar';
6
+
7
+ const DEFAULT_SIZE = 20;
8
+
9
+ /**
10
+ * Alia's live avatar as a Bloom icon: pass it as `icon` / `leadingIcon` to a Bloom `Button` and
11
+ * the button shows her, reacting as she thinks, works and writes (`useAliaActivity`) — also while
12
+ * the chat sheet is closed.
13
+ *
14
+ * ```tsx
15
+ * <Button icon={AliaAvatarIcon} onPress={openAlia}>Ask Alia</Button>
16
+ * ```
17
+ *
18
+ * It mounts its own `AgentAvatarProvider` on `ALIA_CHARACTER_RUNTIME_URL`: an avatar outside the
19
+ * chat's provider would ask the host for `/bloom-character/runtime.mjs` and get its HTML. The
20
+ * colour (`fill`) is ignored — the likeness is painted — and it is never interactive, so the
21
+ * button keeps the press. Reduced motion is Bloom's engine's to honour.
22
+ */
23
+ export const AliaAvatarIcon: BloomIconComponent = ({ width, height }) => {
24
+ const state = useAliaActivity();
25
+ return (
26
+ <AgentAvatarProvider runtimeUrl={ALIA_CHARACTER_RUNTIME_URL}>
27
+ <AliaAvatar size={width ?? height ?? DEFAULT_SIZE} state={state} />
28
+ </AgentAvatarProvider>
29
+ );
30
+ };