@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.
- package/CHANGELOG.md +190 -0
- package/README.md +83 -20
- package/package.json +8 -4
- package/src/components/AliaAvatarIcon.tsx +30 -0
- package/src/components/AliaChatContent.tsx +57 -107
- package/src/components/AliaChatMessageList.tsx +2 -10
- package/src/components/AliaChatScreen.tsx +28 -25
- package/src/components/AliaChatSheet.tsx +30 -8
- package/src/components/AliaComposer.tsx +382 -0
- package/src/components/ComposerDropZone.tsx +235 -0
- package/src/components/voice/VoiceControls.tsx +28 -4
- package/src/components/voice/VoiceSession.tsx +23 -7
- package/src/hooks/useAliaActivity.ts +31 -0
- package/src/hooks/useAliaChat.ts +14 -3
- package/src/hooks/useAmbientWave.ts +3 -3
- package/src/hooks/useAttachmentIntake.ts +401 -0
- package/src/hooks/useAudioLevelMonitor.ts +2 -2
- package/src/hooks/useDictation.ts +295 -0
- package/src/hooks/useFocusWhenPresented.ts +41 -0
- package/src/hooks/usePendingQuestions.ts +19 -0
- package/src/hooks/useTTS.ts +24 -63
- package/src/hooks/useVoiceRoom.ts +148 -671
- package/src/index.ts +19 -3
- package/src/lib/activity-socket.ts +102 -0
- package/src/lib/activity-store.ts +53 -0
- package/src/lib/api-url.ts +2 -0
- package/src/lib/attachment-intake.ts +154 -0
- package/src/lib/attachment-tiles.ts +74 -0
- package/src/lib/audio-level.ts +2 -2
- package/src/lib/dictation-recorder-types.ts +37 -0
- package/src/lib/dictation-recorder.native.ts +90 -0
- package/src/lib/dictation-recorder.ts +131 -0
- package/src/lib/dictation.ts +208 -0
- package/src/lib/labels.tsx +50 -1
- package/src/lib/pcm.ts +57 -0
- package/src/lib/speech-language.ts +1 -1
- package/src/lib/speech-messages.ts +43 -65
- package/src/lib/speech-synthesis.ts +104 -9
- package/src/lib/turn-state.ts +15 -0
- package/src/lib/voice-audio-engine-types.ts +59 -0
- package/src/lib/voice-audio-engine.native.ts +232 -0
- package/src/lib/voice-audio-engine.ts +350 -0
- package/src/lib/voice-call.ts +539 -0
- package/src/lib/voice-levels.ts +7 -3
- package/src/lib/voice-protocol.ts +309 -0
- package/src/tools.ts +1 -0
- package/src/types.ts +63 -4
- package/src/voice.ts +31 -10
- package/tsconfig.json +2 -0
- package/src/hooks/useSpeechToText.ts +0 -268
- package/src/lib/speech-recognition-types.ts +0 -108
- package/src/lib/speech-recognition.native.ts +0 -237
- package/src/lib/speech-recognition.ts +0 -308
- package/src/lib/voice-text.ts +0 -198
- 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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
["
|
|
69
|
-
"
|
|
70
|
-
"
|
|
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
|
-
|
|
75
|
-
|
|
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": "
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
+
};
|