@alia.onl/sdk 3.1.7 → 4.0.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 +5 -1
- package/src/components/AliaChatMessageList.tsx +27 -19
- package/src/components/Markdown.tsx +81 -1
- package/src/components/ui/chat-text-input.tsx +12 -8
- package/src/components/ui/prompt-input/prompt-input.tsx +3 -0
- package/src/hooks/useSoundEffects.ts +5 -1
- package/src/hooks/useTTS.ts +24 -11
- package/src/index.ts +1 -17
- package/src/voice.ts +26 -0
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alia.onl/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Alia AI chat SDK — reusable chat UI, voice, 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",
|
|
8
8
|
"types": "src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts",
|
|
11
|
+
"./voice": "./src/voice.ts"
|
|
12
|
+
},
|
|
9
13
|
"files": [
|
|
10
14
|
"src",
|
|
11
15
|
"tsconfig.json"
|
|
@@ -37,8 +37,14 @@ type ExpoImageProps = {
|
|
|
37
37
|
className?: string;
|
|
38
38
|
contentFit?: string;
|
|
39
39
|
};
|
|
40
|
+
// expo-image is an optional peer dependency: SDK-only consumers that never render
|
|
41
|
+
// inline images may not install it. Degrade to skipping images when it can't be required.
|
|
40
42
|
let ExpoImage: React.ComponentType<ExpoImageProps> | null = null;
|
|
41
|
-
try {
|
|
43
|
+
try {
|
|
44
|
+
ExpoImage = require('expo-image').Image;
|
|
45
|
+
} catch {
|
|
46
|
+
// Optional peer dependency absent — inline images are simply not rendered.
|
|
47
|
+
}
|
|
42
48
|
|
|
43
49
|
type PlaybackState = 'idle' | 'loading' | 'playing' | 'paused' | 'error';
|
|
44
50
|
|
|
@@ -93,7 +99,7 @@ function ToolBullet({ isRunning }: { isRunning: boolean }) {
|
|
|
93
99
|
|
|
94
100
|
// ── User Bubble ──
|
|
95
101
|
|
|
96
|
-
function UserBubble({
|
|
102
|
+
const UserBubble = React.memo(function UserBubble({
|
|
97
103
|
message,
|
|
98
104
|
text,
|
|
99
105
|
isEditing,
|
|
@@ -111,10 +117,10 @@ function UserBubble({
|
|
|
111
117
|
isEditing: boolean;
|
|
112
118
|
editedContent: string;
|
|
113
119
|
onEditedContentChange: (text: string) => void;
|
|
114
|
-
onSaveEdit: () => void;
|
|
120
|
+
onSaveEdit: (messageId: string) => void;
|
|
115
121
|
onCancelEdit: () => void;
|
|
116
|
-
onStartEdit: () => void;
|
|
117
|
-
onCopy: () => void;
|
|
122
|
+
onStartEdit: (messageId: string, content: string) => void;
|
|
123
|
+
onCopy: (text: string, messageId: string) => void;
|
|
118
124
|
copiedMessageId: string | null;
|
|
119
125
|
showEditButton: boolean;
|
|
120
126
|
}) {
|
|
@@ -136,7 +142,7 @@ function UserBubble({
|
|
|
136
142
|
<View className="flex-row gap-2 mt-2">
|
|
137
143
|
<Pressable
|
|
138
144
|
className="px-3 py-1.5 rounded-lg bg-primary"
|
|
139
|
-
onPress={onSaveEdit}
|
|
145
|
+
onPress={() => onSaveEdit(message.id)}
|
|
140
146
|
>
|
|
141
147
|
<Text className="text-xs text-primary-foreground">Save</Text>
|
|
142
148
|
</Pressable>
|
|
@@ -179,7 +185,7 @@ function UserBubble({
|
|
|
179
185
|
<View className="flex-row gap-1">
|
|
180
186
|
<Pressable
|
|
181
187
|
className="p-1.5 rounded-lg active:bg-muted"
|
|
182
|
-
onPress={onCopy}
|
|
188
|
+
onPress={() => onCopy(text, message.id)}
|
|
183
189
|
>
|
|
184
190
|
{copiedMessageId === message.id ? (
|
|
185
191
|
<Check size={14} className="text-green-500" />
|
|
@@ -190,7 +196,7 @@ function UserBubble({
|
|
|
190
196
|
{showEditButton && (
|
|
191
197
|
<Pressable
|
|
192
198
|
className="p-1.5 rounded-lg active:bg-muted"
|
|
193
|
-
onPress={onStartEdit}
|
|
199
|
+
onPress={() => onStartEdit(message.id, text)}
|
|
194
200
|
>
|
|
195
201
|
<Pencil size={14} className="text-muted-foreground" />
|
|
196
202
|
</Pressable>
|
|
@@ -199,11 +205,11 @@ function UserBubble({
|
|
|
199
205
|
)}
|
|
200
206
|
</View>
|
|
201
207
|
);
|
|
202
|
-
}
|
|
208
|
+
});
|
|
203
209
|
|
|
204
210
|
// ── Assistant Message ──
|
|
205
211
|
|
|
206
|
-
function AssistantMessage({
|
|
212
|
+
const AssistantMessage = React.memo(function AssistantMessage({
|
|
207
213
|
message,
|
|
208
214
|
messageText,
|
|
209
215
|
isStreamingThis,
|
|
@@ -229,7 +235,7 @@ function AssistantMessage({
|
|
|
229
235
|
onReadAloud?: (messageId: string, text: string) => void;
|
|
230
236
|
ttsActiveMessageId?: string | null;
|
|
231
237
|
ttsPlaybackState?: PlaybackState;
|
|
232
|
-
onCopy: () => void;
|
|
238
|
+
onCopy: (text: string, messageId: string) => void;
|
|
233
239
|
copiedMessageId: string | null;
|
|
234
240
|
onThumbsUp?: (messageId: string) => void;
|
|
235
241
|
onThumbsDown?: (messageId: string) => void;
|
|
@@ -377,7 +383,7 @@ function AssistantMessage({
|
|
|
377
383
|
)}
|
|
378
384
|
<Pressable
|
|
379
385
|
className="p-1.5 rounded-lg active:bg-muted"
|
|
380
|
-
onPress={onCopy}
|
|
386
|
+
onPress={() => onCopy(messageText, message.id)}
|
|
381
387
|
>
|
|
382
388
|
{copiedMessageId === message.id ? (
|
|
383
389
|
<Check size={14} className="text-green-500" />
|
|
@@ -403,7 +409,7 @@ function AssistantMessage({
|
|
|
403
409
|
{thinkingIndicator}
|
|
404
410
|
</View>
|
|
405
411
|
);
|
|
406
|
-
}
|
|
412
|
+
});
|
|
407
413
|
|
|
408
414
|
// ── List ──
|
|
409
415
|
|
|
@@ -460,7 +466,9 @@ export function AliaChatMessageList({
|
|
|
460
466
|
}
|
|
461
467
|
setCopiedMessageId(messageId);
|
|
462
468
|
setTimeout(() => setCopiedMessageId(null), 2000);
|
|
463
|
-
} catch {
|
|
469
|
+
} catch (err) {
|
|
470
|
+
console.warn('[AliaChat] Failed to copy message to clipboard:', err);
|
|
471
|
+
}
|
|
464
472
|
}, []);
|
|
465
473
|
|
|
466
474
|
const handleStartEdit = useCallback((messageId: string, content: string) => {
|
|
@@ -520,12 +528,12 @@ export function AliaChatMessageList({
|
|
|
520
528
|
message={msg}
|
|
521
529
|
text={messageText}
|
|
522
530
|
isEditing={editingMessageId === msg.id}
|
|
523
|
-
editedContent={editedContent}
|
|
531
|
+
editedContent={editingMessageId === msg.id ? editedContent : ''}
|
|
524
532
|
onEditedContentChange={setEditedContent}
|
|
525
|
-
onSaveEdit={
|
|
533
|
+
onSaveEdit={handleSaveEdit}
|
|
526
534
|
onCancelEdit={handleCancelEdit}
|
|
527
|
-
onStartEdit={
|
|
528
|
-
onCopy={
|
|
535
|
+
onStartEdit={handleStartEdit}
|
|
536
|
+
onCopy={handleCopy}
|
|
529
537
|
copiedMessageId={copiedMessageId}
|
|
530
538
|
showEditButton={!!onEditMessage}
|
|
531
539
|
/>
|
|
@@ -539,7 +547,7 @@ export function AliaChatMessageList({
|
|
|
539
547
|
onReadAloud={onReadAloud}
|
|
540
548
|
ttsActiveMessageId={ttsActiveMessageId}
|
|
541
549
|
ttsPlaybackState={ttsPlaybackState}
|
|
542
|
-
onCopy={
|
|
550
|
+
onCopy={handleCopy}
|
|
543
551
|
copiedMessageId={copiedMessageId}
|
|
544
552
|
onThumbsUp={onThumbsUp}
|
|
545
553
|
onThumbsDown={onThumbsDown}
|
|
@@ -240,6 +240,72 @@ export interface AliaMarkdownProps {
|
|
|
240
240
|
colors?: Partial<AliaColors>;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
const FENCE_RE = /^\s*(?:```|~~~)/;
|
|
244
|
+
const LIST_OR_TABLE_RE = /^\s*(?:[-*+]\s|\d+[.)]\s|\|)/;
|
|
245
|
+
// Link reference definitions ([1]: https://…) resolve per-parse, so a document
|
|
246
|
+
// using them cannot be split into independently parsed blocks.
|
|
247
|
+
const LINK_REF_DEF_RE = /^\s*\[[^\]]+\]:\s/m;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Split markdown into top-level blocks at blank-line boundaries, keeping
|
|
251
|
+
* fenced code, list runs, and tables intact. During streaming only the last
|
|
252
|
+
* block's text changes, so every completed block hits React.memo and skips
|
|
253
|
+
* its markdown-it re-parse — the per-flush cost drops from O(document) to
|
|
254
|
+
* O(trailing block).
|
|
255
|
+
*/
|
|
256
|
+
function splitMarkdownBlocks(content: string): string[] {
|
|
257
|
+
if (LINK_REF_DEF_RE.test(content)) return [content];
|
|
258
|
+
|
|
259
|
+
const lines = content.split('\n');
|
|
260
|
+
const blocks: string[] = [];
|
|
261
|
+
let current: string[] = [];
|
|
262
|
+
let lastNonBlank = '';
|
|
263
|
+
let inFence = false;
|
|
264
|
+
let i = 0;
|
|
265
|
+
|
|
266
|
+
while (i < lines.length) {
|
|
267
|
+
const line = lines[i];
|
|
268
|
+
if (FENCE_RE.test(line)) {
|
|
269
|
+
inFence = !inFence;
|
|
270
|
+
current.push(line);
|
|
271
|
+
lastNonBlank = line;
|
|
272
|
+
i += 1;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (!inFence && line.trim() === '') {
|
|
276
|
+
let j = i;
|
|
277
|
+
while (j < lines.length && lines[j].trim() === '') j += 1;
|
|
278
|
+
const next = j < lines.length ? lines[j] : '';
|
|
279
|
+
// Splitting a list or table at a blank line would restart ordered-list
|
|
280
|
+
// numbering and break table parsing — keep those runs in one block.
|
|
281
|
+
const keepTogether = LIST_OR_TABLE_RE.test(lastNonBlank) || LIST_OR_TABLE_RE.test(next);
|
|
282
|
+
if (keepTogether || current.length === 0) {
|
|
283
|
+
for (; i < j; i += 1) current.push(lines[i]);
|
|
284
|
+
} else {
|
|
285
|
+
blocks.push(current.join('\n'));
|
|
286
|
+
current = [];
|
|
287
|
+
i = j;
|
|
288
|
+
}
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
current.push(line);
|
|
292
|
+
lastNonBlank = line;
|
|
293
|
+
i += 1;
|
|
294
|
+
}
|
|
295
|
+
if (current.length > 0) blocks.push(current.join('\n'));
|
|
296
|
+
return blocks.length > 0 ? blocks : [content];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
type MarkdownBlockProps = {
|
|
300
|
+
content: string;
|
|
301
|
+
rules: ReturnType<typeof createRules>;
|
|
302
|
+
styles: ReturnType<typeof createStyles>;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const MarkdownBlock = React.memo(function MarkdownBlock({ content, rules, styles }: MarkdownBlockProps) {
|
|
306
|
+
return <Markdown rules={rules} style={styles}>{content}</Markdown>;
|
|
307
|
+
});
|
|
308
|
+
|
|
243
309
|
export function AliaMarkdown({ content, colors: colorOverrides }: AliaMarkdownProps) {
|
|
244
310
|
const scheme = useColorScheme();
|
|
245
311
|
const fallback = scheme === 'dark' ? FALLBACK_DARK : FALLBACK_LIGHT;
|
|
@@ -247,6 +313,20 @@ export function AliaMarkdown({ content, colors: colorOverrides }: AliaMarkdownPr
|
|
|
247
313
|
|
|
248
314
|
const customRules = useMemo(() => createRules(colors), [colors.text, colors.muted, colors.border, colors.primary, colors.mutedForeground]);
|
|
249
315
|
const markdownStyles = useMemo(() => createStyles(colors), [colors.text, colors.muted, colors.border, colors.primary, colors.mutedForeground]);
|
|
316
|
+
const blocks = useMemo(() => splitMarkdownBlocks(content), [content]);
|
|
317
|
+
|
|
318
|
+
if (blocks.length === 1) {
|
|
319
|
+
return <Markdown rules={customRules} style={markdownStyles}>{blocks[0]}</Markdown>;
|
|
320
|
+
}
|
|
250
321
|
|
|
251
|
-
|
|
322
|
+
// Each block's `body` rule cancels its own trailing paragraph margin
|
|
323
|
+
// (marginBottom: -8), so rowGap restores the exact inter-paragraph spacing
|
|
324
|
+
// a single continuous parse would produce.
|
|
325
|
+
return (
|
|
326
|
+
<View style={{ rowGap: 8 }}>
|
|
327
|
+
{blocks.map((block, i) => (
|
|
328
|
+
<MarkdownBlock key={i} content={block} rules={customRules} styles={markdownStyles} />
|
|
329
|
+
))}
|
|
330
|
+
</View>
|
|
331
|
+
);
|
|
252
332
|
}
|
|
@@ -11,6 +11,15 @@ import {
|
|
|
11
11
|
} from "react-native";
|
|
12
12
|
import { cn } from "../../lib/utils";
|
|
13
13
|
|
|
14
|
+
// react-native-web forwards the DOM KeyboardEvent modifier flags on the key-press
|
|
15
|
+
// nativeEvent, but React Native's `TextInputKeyPressEventData` only declares `key`.
|
|
16
|
+
// Augment the type (web-only field, hence optional) so `shiftKey` is readable without a cast.
|
|
17
|
+
declare module "react-native" {
|
|
18
|
+
interface TextInputKeyPressEventData {
|
|
19
|
+
shiftKey?: boolean;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
type ChatTextInputProps = React.ComponentPropsWithoutRef<typeof TextInput> & {
|
|
15
24
|
noFocus?: boolean;
|
|
16
25
|
onEnterPress?: () => void;
|
|
@@ -54,13 +63,11 @@ const ChatTextInput = React.forwardRef<TextInput, ChatTextInputProps>(
|
|
|
54
63
|
const handlePaste = (e: Event) => {
|
|
55
64
|
const clipboardEvent = e as ClipboardEvent;
|
|
56
65
|
|
|
57
|
-
// @ts-ignore - web-specific API
|
|
58
66
|
const activeElement = document.activeElement;
|
|
59
|
-
// @ts-ignore - web-specific API
|
|
60
67
|
const wrapper = wrapperRef.current;
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
-
const isContained = wrapper && wrapper.contains(activeElement);
|
|
68
|
+
// On web the wrapper ref resolves to its underlying DOM node — only react to
|
|
69
|
+
// pastes whose focused element lives inside this input.
|
|
70
|
+
const isContained = wrapper instanceof HTMLElement && wrapper.contains(activeElement);
|
|
64
71
|
|
|
65
72
|
if (!isContained) return;
|
|
66
73
|
|
|
@@ -84,11 +91,9 @@ const ChatTextInput = React.forwardRef<TextInput, ChatTextInputProps>(
|
|
|
84
91
|
}
|
|
85
92
|
};
|
|
86
93
|
|
|
87
|
-
// @ts-ignore - web-specific API
|
|
88
94
|
document.addEventListener('paste', handlePaste);
|
|
89
95
|
|
|
90
96
|
return () => {
|
|
91
|
-
// @ts-ignore - web-specific API
|
|
92
97
|
document.removeEventListener('paste', handlePaste);
|
|
93
98
|
};
|
|
94
99
|
}, [onImagePaste]);
|
|
@@ -108,7 +113,6 @@ const ChatTextInput = React.forwardRef<TextInput, ChatTextInputProps>(
|
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
if (key === "Enter" && !disableEnterToSubmit) {
|
|
111
|
-
// @ts-ignore - shiftKey exists on web
|
|
112
116
|
if (Platform.OS !== 'web' || !e.nativeEvent.shiftKey) {
|
|
113
117
|
e.preventDefault();
|
|
114
118
|
onEnterPress?.();
|
|
@@ -133,6 +133,9 @@ export function PromptInput({
|
|
|
133
133
|
|
|
134
134
|
const handleSubmit = () => {
|
|
135
135
|
onSubmit?.();
|
|
136
|
+
// Clicking the send button (as opposed to pressing Enter) shifts DOM focus
|
|
137
|
+
// to the button on web — restore it so the user can keep typing.
|
|
138
|
+
textareaRef.current?.focus();
|
|
136
139
|
if (showFullscreen) setShowFullscreen(false);
|
|
137
140
|
};
|
|
138
141
|
|
|
@@ -59,7 +59,11 @@ export function useSoundEffects(enabled: boolean = false, sounds: SoundSources =
|
|
|
59
59
|
return () => {
|
|
60
60
|
mounted = false;
|
|
61
61
|
for (const player of playersRef.current.values()) {
|
|
62
|
-
try {
|
|
62
|
+
try {
|
|
63
|
+
player.release();
|
|
64
|
+
} catch {
|
|
65
|
+
// Best-effort cleanup — a player already released/unloaded is safe to ignore.
|
|
66
|
+
}
|
|
63
67
|
}
|
|
64
68
|
playersRef.current.clear();
|
|
65
69
|
loadedRef.current = false;
|
package/src/hooks/useTTS.ts
CHANGED
|
@@ -55,15 +55,16 @@ export function useTTS(options: UseTTSOptions = {}) {
|
|
|
55
55
|
const tonePref = options.tone ?? 'default';
|
|
56
56
|
|
|
57
57
|
const { oxyServices } = useOxy();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
// Per-field selectors: subscribing to the whole store re-renders every
|
|
59
|
+
// consumer on any playback transition. Actions are static store refs, so
|
|
60
|
+
// selecting them individually never triggers a re-render.
|
|
61
|
+
const activeMessageId = useTTSStore((s) => s.activeMessageId);
|
|
62
|
+
const playbackState = useTTSStore((s) => s.playbackState);
|
|
63
|
+
const error = useTTSStore((s) => s.error);
|
|
64
|
+
const setActiveMessage = useTTSStore((s) => s.setActiveMessage);
|
|
65
|
+
const setPlaybackState = useTTSStore((s) => s.setPlaybackState);
|
|
66
|
+
const setError = useTTSStore((s) => s.setError);
|
|
67
|
+
const reset = useTTSStore((s) => s.reset);
|
|
67
68
|
|
|
68
69
|
const playerRef = useRef<AudioPlayer | null>(null);
|
|
69
70
|
|
|
@@ -108,7 +109,9 @@ export function useTTS(options: UseTTSOptions = {}) {
|
|
|
108
109
|
const releasePlayer = useCallback(() => {
|
|
109
110
|
try {
|
|
110
111
|
playerRef.current?.remove();
|
|
111
|
-
} catch {
|
|
112
|
+
} catch {
|
|
113
|
+
// Best-effort teardown — the player may already be removed.
|
|
114
|
+
}
|
|
112
115
|
playerRef.current = null;
|
|
113
116
|
}, []);
|
|
114
117
|
|
|
@@ -127,6 +130,11 @@ export function useTTS(options: UseTTSOptions = {}) {
|
|
|
127
130
|
playerRef.current = player;
|
|
128
131
|
|
|
129
132
|
player.addListener('playbackStatusUpdate', (status: AudioStatus) => {
|
|
133
|
+
if (status.error) {
|
|
134
|
+
releasePlayer();
|
|
135
|
+
setError(status.error);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
130
138
|
if (status.didJustFinish) {
|
|
131
139
|
releasePlayer();
|
|
132
140
|
reset();
|
|
@@ -147,6 +155,11 @@ export function useTTS(options: UseTTSOptions = {}) {
|
|
|
147
155
|
conversationId?: string,
|
|
148
156
|
audioUrl?: string,
|
|
149
157
|
) => {
|
|
158
|
+
// Read volatile playback state at call time (not via closure) so this
|
|
159
|
+
// callback's identity survives every playback transition. It is passed to
|
|
160
|
+
// memoized message rows — an unstable identity re-renders all of them.
|
|
161
|
+
const { activeMessageId, playbackState } = useTTSStore.getState();
|
|
162
|
+
|
|
150
163
|
// If same message is playing, toggle pause/play
|
|
151
164
|
if (activeMessageId === messageId) {
|
|
152
165
|
if (playbackState === 'playing') {
|
|
@@ -212,7 +225,7 @@ export function useTTS(options: UseTTSOptions = {}) {
|
|
|
212
225
|
console.error('[TTS] Error:', e);
|
|
213
226
|
setError(errorMessage(e, 'Failed to read aloud'));
|
|
214
227
|
}
|
|
215
|
-
}, [
|
|
228
|
+
}, [getToken, apiUrl, getTTSVoice, getTTSSpeed, stop, playFromUrl, setActiveMessage, setPlaybackState, setError]);
|
|
216
229
|
|
|
217
230
|
// Cleanup on unmount
|
|
218
231
|
useEffect(() => {
|
package/src/index.ts
CHANGED
|
@@ -19,27 +19,15 @@ export { AliaWelcomeMessage } from './components/AliaWelcomeMessage';
|
|
|
19
19
|
export { ResearchProgressCard } from './components/ResearchProgressCard';
|
|
20
20
|
export { PlanPreviewCard } from './components/PlanPreviewCard';
|
|
21
21
|
|
|
22
|
-
// ── Voice components ──
|
|
23
|
-
export { AudioWaveVisualizer } from './components/voice/AudioWaveVisualizer';
|
|
24
|
-
export { VoiceOverlay } from './components/voice/VoiceOverlay';
|
|
25
|
-
export { VoiceControls } from './components/voice/VoiceControls';
|
|
26
|
-
|
|
27
22
|
// ── Chat hook ──
|
|
28
23
|
export { useAliaChat } from './hooks/useAliaChat';
|
|
29
24
|
export type { UseAliaChatOptions, UseAliaChatReturn } from './hooks/useAliaChat';
|
|
30
25
|
|
|
31
|
-
// ──
|
|
32
|
-
export { useVoiceRoom } from './hooks/useVoiceRoom';
|
|
33
|
-
export { useAudioLevelMonitor } from './hooks/useAudioLevelMonitor';
|
|
34
|
-
export { useAudioLevels } from './hooks/useAudioLevels';
|
|
26
|
+
// ── Text-to-speech / speech-to-text (no livekit) ──
|
|
35
27
|
export { useTTS } from './hooks/useTTS';
|
|
36
28
|
export type { UseTTSOptions } from './hooks/useTTS';
|
|
37
29
|
export { useSpeechToText, useSTTStore } from './hooks/useSpeechToText';
|
|
38
30
|
export type { UseSTTOptions } from './hooks/useSpeechToText';
|
|
39
|
-
export { useAmbientWave } from './hooks/useAmbientWave';
|
|
40
|
-
export type { UseAmbientWaveOptions, UseAmbientWaveResult, AmbientWaveVoice, AmbientWaveMode } from './hooks/useAmbientWave';
|
|
41
|
-
export { useSoundEffects, useVoiceSoundEffects } from './hooks/useSoundEffects';
|
|
42
|
-
export type { SoundName, SoundSources } from './hooks/useSoundEffects';
|
|
43
31
|
|
|
44
32
|
// ── Types ──
|
|
45
33
|
export type {
|
|
@@ -50,10 +38,6 @@ export type {
|
|
|
50
38
|
ResearchSource,
|
|
51
39
|
PendingPlan,
|
|
52
40
|
PlanStep,
|
|
53
|
-
RoomState,
|
|
54
|
-
AgentState,
|
|
55
|
-
VoiceMessage,
|
|
56
|
-
VoiceToolInvocation,
|
|
57
41
|
} from './types';
|
|
58
42
|
|
|
59
43
|
// ── UI Primitives (NativeWind) ──
|
package/src/voice.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @alia.onl/sdk/voice — Alia voice surface
|
|
2
|
+
// LiveKit-backed voice room, audio visualisation, ambient wave, and sound effects.
|
|
3
|
+
// Split out of the root entry so text-chat consumers never pull livekit-client
|
|
4
|
+
// into their module graph.
|
|
5
|
+
|
|
6
|
+
// ── Voice components ──
|
|
7
|
+
export { AudioWaveVisualizer } from './components/voice/AudioWaveVisualizer';
|
|
8
|
+
export { VoiceOverlay } from './components/voice/VoiceOverlay';
|
|
9
|
+
export { VoiceControls } from './components/voice/VoiceControls';
|
|
10
|
+
|
|
11
|
+
// ── Voice hooks ──
|
|
12
|
+
export { useVoiceRoom } from './hooks/useVoiceRoom';
|
|
13
|
+
export { useAudioLevelMonitor } from './hooks/useAudioLevelMonitor';
|
|
14
|
+
export { useAudioLevels } from './hooks/useAudioLevels';
|
|
15
|
+
export { useAmbientWave } from './hooks/useAmbientWave';
|
|
16
|
+
export type { UseAmbientWaveOptions, UseAmbientWaveResult, AmbientWaveVoice, AmbientWaveMode } from './hooks/useAmbientWave';
|
|
17
|
+
export { useSoundEffects, useVoiceSoundEffects } from './hooks/useSoundEffects';
|
|
18
|
+
export type { SoundName, SoundSources } from './hooks/useSoundEffects';
|
|
19
|
+
|
|
20
|
+
// ── Voice types ──
|
|
21
|
+
export type {
|
|
22
|
+
RoomState,
|
|
23
|
+
AgentState,
|
|
24
|
+
VoiceMessage,
|
|
25
|
+
VoiceToolInvocation,
|
|
26
|
+
} from './types';
|