@unith-ai/core-client 1.3.0 → 1.4.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/README.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # Unith Core Client Typescript SDK
2
2
 
3
- An SDK library for building complex digital human experiences using javascript/typescript.
3
+ An SDK library for building complex digital human experiences using javascript/typescript that run on [Unith AI](https://www.unith.ai/).
4
+
5
+ ## Prerequisite
6
+
7
+ Before proceeding with using this library, you're expected to have an account on [Unith AI](https://www.unith.ai/), create a digital human and take note of your API key. You can create an account [here](https://app.unith.ai/) in minutes!
4
8
 
5
9
  ## Installation
6
10
 
7
11
  Install the package in your project through package manager.
12
+
8
13
  ```shell
9
14
  npm install @unith-ai/core-client
10
15
  # or
@@ -20,33 +25,25 @@ This library is designed for use in plain JavaScript applications or to serve as
20
25
  ### Initialize Digital Human
21
26
 
22
27
  First, initialize the Conversation instance:
28
+
23
29
  ```js
24
30
  const conversation = await Conversation.startDigitalHuman(options);
25
31
  ```
26
32
 
27
- This will establish a WebSocket connection and initialize the digital human avatar with audio/video streaming capabilities.
33
+ This will establish a WebSocket connection and initialize the digital human with realtime audio & video streaming capabilities.
28
34
 
29
35
  #### Session Configuration
30
36
 
31
37
  The options passed to `startDigitalHuman` specify how the session is established:
38
+
32
39
  ```js
33
40
  const conversation = await Conversation.startDigitalHuman({
34
41
  orgId: "your-org-id",
35
42
  headId: "your-head-id",
36
- username: "anonymous",
37
- password: "Password1",
38
- environment: "production", // or "development"
39
43
  element: document.getElementById("video-container"), // HTML element for video output
40
44
  apiKey: "your-api-key",
41
- mode: "default",
42
- frameRate: 30,
43
- streamType: "jpg", // or "vp8"
44
- quality: "high",
45
- crop: false,
46
- showIdle: false,
47
- language: "en",
48
45
  allowWakeLock: true,
49
- fadeTransitionsType: VideoTransitionType.NONE,
46
+ ...callbacks,
50
47
  });
51
48
  ```
52
49
 
@@ -54,106 +51,52 @@ const conversation = await Conversation.startDigitalHuman({
54
51
 
55
52
  - **orgId** - Your organization ID
56
53
  - **headId** - The digital human head ID to use
54
+ - **apiKey** - API key for authentication (default: "")
57
55
  - **element** - HTML element where the video will be rendered
58
- - **username** - Authentication username (default: "anonymous")
59
- - **password** - Authentication password (default: "Password1")
60
56
 
61
57
  #### Optional Parameters
62
58
 
63
- - **environment** - API environment ("production" or "development", default: "production")
64
- - **apiKey** - API key for authentication (default: "")
65
59
  - **mode** - Conversation mode (default: "default")
66
- - **frameRate** - Video frame rate (default: 30)
67
- - **streamType** - Video stream format ("jpg" or "vp8", default: "jpg")
68
- - **quality** - Video quality ("high", "medium", or "low", default: "high")
69
- - **crop** - Whether to crop the video (default: false)
70
- - **showIdle** - Whether to show idle video when not speaking (default: false)
71
60
  - **language** - Language code for the conversation (default: browser language)
72
61
  - **allowWakeLock** - Prevent screen from sleeping during conversation (default: true)
73
- - **fadeTransitionsType** - Video transition type (default: VideoTransitionType.NONE)
74
62
 
75
- #### Optional Callbacks
63
+ #### Callbacks
76
64
 
77
65
  Register callbacks to handle various events:
78
- ```js
79
- const conversation = await Conversation.startDigitalHuman({
80
- // ... required options
81
- onConnect: ({ userId, headInfo, microphoneAccess }) => {
82
- console.log("Connected:", userId);
83
- },
84
- onDisconnect: (details) => {
85
- console.log("Disconnected:", details.reason);
86
- },
87
- onStatusChange: ({ status }) => {
88
- console.log("Status changed:", status); // "connecting", "connected", "disconnecting", "disconnected"
89
- },
90
- onText: (event) => {
91
- console.log("User message:", event.text);
92
- },
93
- onResponse: (event) => {
94
- console.log("AI response:", event.text);
95
- },
96
- onJoin: (event) => {
97
- console.log("Joined conversation:", event);
98
- },
99
- onStreaming: (event) => {
100
- console.log("Streaming event:", event.type); // "audio_frame", "video_frame", "metadata", "cache", "error"
101
- },
102
- onMuteStatusChange: ({ isMuted }) => {
103
- console.log("Mute status:", isMuted);
104
- },
105
- onSpeakingStart: () => {
106
- console.log("Digital human started speaking");
107
- },
108
- onSpeakingEnd: () => {
109
- console.log("Digital human stopped speaking");
110
- },
111
- onStoppingEnd: () => {
112
- console.log("Response stopped");
113
- },
114
- onTimeout: () => {
115
- console.log("Session timed out");
116
- },
117
- onTimeoutWarning: () => {
118
- console.log("Session will timeout soon");
119
- },
120
- onKeepSession: ({ granted }) => {
121
- console.log("Keep session request:", granted);
122
- },
123
- onError: ({ message, endConversation, type }) => {
124
- console.error("Error:", message);
125
- // type: "toast" or "modal"
126
- // endConversation: true if session should be restarted
127
- },
128
- });
129
- ```
130
-
131
- #### Event Types
132
66
 
133
- - **onConnect** - Called when the WebSocket connection is established
134
- - **onDisconnect** - Called when the connection is closed
135
- - **onStatusChange** - Called when connection status changes
136
- - **onText** - Called when a user text message is received
137
- - **onResponse** - Called when the AI generates a response
138
- - **onJoin** - Called when successfully joining the conversation
139
- - **onStreaming** - Called for audio/video frame events
67
+ - **onConnect ({userId, headInfo, microphoneAccess})** - Called when the WebSocket connection is established
68
+ - **userId** `Boolean` Unique Identifier for the users session.
69
+ - **headInfo** `ConnectHeadType` Object with data about the digital human.
70
+ - **name** `String` Digital human head name
71
+ - **phrases** `String[]` Array with phrases set during digital human creation.
72
+ - **language** `String` Language code setup during digital human creation.
73
+ - **avatar** `String` Static image url for digital human.
74
+ - **microphoneAccess** `Boolean` True if microphone access was granted, False otherwise.
75
+ - **onDisconnect ()** - Called when the connection is closed
76
+ - **onStatusChange ({status})** - Called when connection status changes
77
+ - **status** `"connecting" | "connected" | "disconnecting" | "disconnected"` Shows current websocket connection status.
78
+ - **onMessage ({ timestamp, speaker, text, visible })** - Called when websocket receives a message or sends a response.
79
+ - **timestamp** `Date` Timestamp when message was received/sent
80
+ - **sender** `"user" | "ai"` Shows who the message came from.
81
+ - **text** `String` Message text
82
+ - **visible** `Boolean` Flag that you can use to control visibility of message. Sometimes, message comes before the video response starts playing. In such cases, this is usually `false`. Listen the `onSpeakingStart` event to change visibility when the video response starts playing.
140
83
  - **onMuteStatusChange** - Called when mute status changes
141
84
  - **onSpeakingStart** - Called when the digital human starts speaking
142
85
  - **onSpeakingEnd** - Called when the digital human finishes speaking
143
86
  - **onStoppingEnd** - Called when a response is manually stopped
144
87
  - **onTimeout** - Called when the session times out due to inactivity
145
- - **onTimeoutWarning** - Called before the session times out
88
+ - **onTimeoutWarning** - Called before the session times out. This event warns you that the customers session is going to end in a bit. You can call the `keepSession` method to extend the customers session.
146
89
  - **onKeepSession** - Called when a keep-alive request is processed
147
90
  - **onError** - Called when an error occurs
148
91
 
149
92
  ### Getting Background Video
150
93
 
151
94
  Retrieve the idle background video URL for use in welcome screens or widget mode:
95
+
152
96
  ```js
153
97
  const videoUrl = await Conversation.getBackgroundVideo({
154
98
  orgId: "your-org-id",
155
99
  headId: "your-head-id",
156
- environment: "production",
157
100
  });
158
101
  ```
159
102
 
@@ -161,7 +104,8 @@ const videoUrl = await Conversation.getBackgroundVideo({
161
104
 
162
105
  #### startSession()
163
106
 
164
- Start the conversation session and begin audio playback:
107
+ Start the conversation session and begin audio & video playback:
108
+
165
109
  ```js
166
110
  await conversation.startSession();
167
111
  ```
@@ -171,40 +115,23 @@ This method should be called after user interaction to ensure audio context is p
171
115
  #### sendMessage(message)
172
116
 
173
117
  Send a text message to the digital human:
118
+
174
119
  ```js
175
- conversation.sendMessage({
176
- id: 1,
177
- timestamp: new Date().toISOString(),
178
- speaker: "user",
179
- text: "Hello, how are you?",
180
- isSent: false,
181
- user_id: "user-123",
182
- username: "John Doe",
183
- event: EventType.TEXT,
184
- visible: true,
185
- });
120
+ conversation.sendMessage("Hello, how are you?");
186
121
  ```
187
122
 
188
- #### keepSession(message)
123
+ #### keepSession()
124
+
125
+ Sends keep-alive event to prevent session timeout:
189
126
 
190
- Send a keep-alive message to prevent session timeout:
191
127
  ```js
192
- conversation.keepSession({
193
- id: 1,
194
- timestamp: new Date().toISOString(),
195
- speaker: "user",
196
- text: "",
197
- isSent: false,
198
- user_id: "user-123",
199
- username: "John Doe",
200
- event: EventType.KEEP_SESSION,
201
- visible: true,
202
- });
128
+ conversation.keepSession();
203
129
  ```
204
130
 
205
131
  #### stopCurrentResponse()
206
132
 
207
133
  Stop the current response from the digital human:
134
+
208
135
  ```js
209
136
  conversation.stopCurrentResponse();
210
137
  ```
@@ -214,6 +141,7 @@ This clears both audio and video queues and returns the digital human to idle st
214
141
  #### toggleMuteStatus()
215
142
 
216
143
  Toggle the mute status of the audio output:
144
+
217
145
  ```js
218
146
  const volume = await conversation.toggleMuteStatus();
219
147
  console.log("New volume:", volume); // 0 for muted, 1 for unmuted
@@ -222,6 +150,7 @@ console.log("New volume:", volume); // 0 for muted, 1 for unmuted
222
150
  #### getUserId()
223
151
 
224
152
  Get the current user's ID:
153
+
225
154
  ```js
226
155
  const userId = conversation.getUserId();
227
156
  ```
@@ -229,76 +158,30 @@ const userId = conversation.getUserId();
229
158
  #### endSession()
230
159
 
231
160
  End the conversation session and clean up resources:
161
+
232
162
  ```js
233
163
  await conversation.endSession();
234
164
  ```
235
165
 
236
166
  This closes the WebSocket connection, releases the wake lock, and destroys audio/video outputs.
237
167
 
238
- #### initializeMicrophone()
239
-
240
- Initialize microphone for speech recognition (ASR):
241
- ```js
242
- const asrToken = await conversation.initializeMicrophone(
243
- (result) => {
244
- console.log("Speech result:", result);
245
- },
246
- (error) => {
247
- console.error("Microphone error:", error);
248
- },
249
- (status) => {
250
- console.log("Microphone status:", status);
251
- }
252
- );
253
- ```
254
-
255
168
  ### Message Structure
256
169
 
257
170
  Messages sent to and from the digital human follow this structure:
171
+
258
172
  ```typescript
259
173
  interface Message {
260
- id: number;
261
- timestamp: string; // ISO format
262
- speaker: "user" | "backend";
174
+ timestamp: Date;
175
+ sender: SpeakerType;
263
176
  text: string;
264
- isSent: boolean;
265
- user_id: string;
266
- username: string;
267
- event: EventType.TEXT | EventType.KEEP_SESSION;
268
177
  visible: boolean;
269
- session_id?: string; // Auto-generated
270
178
  }
271
179
  ```
272
180
 
273
- ### Event Types
274
-
275
- The SDK defines the following event types:
276
- ```typescript
277
- enum EventType {
278
- TEXT = "text",
279
- RESPONSE = "response",
280
- JOIN = "join",
281
- STREAMING = "streaming",
282
- BINARY = "binary",
283
- TIMEOUT_WARNING = "timeout_warning",
284
- TIME_OUT = "timeout",
285
- KEEP_SESSION = "keep_session",
286
- }
287
- ```
288
-
289
- ### Streaming Event Types
290
-
291
- Streaming events can have the following types:
292
-
293
- - **audio_frame** - Audio data for playback
294
- - **video_frame** - Video frame data
295
- - **metadata** - Stream control metadata (start/end)
296
- - **cache** - Cached video response
297
- - **error** - Streaming error occurred
298
-
299
181
  ### Error Handling
300
182
 
301
183
  Always handle errors appropriately:
184
+
302
185
  ```js
303
186
  try {
304
187
  const conversation = await Conversation.startDigitalHuman({
@@ -324,106 +207,10 @@ try {
324
207
  }
325
208
  ```
326
209
 
327
- ### Common Error Types
328
-
329
- The SDK handles several error scenarios:
330
-
331
- - **resource_exhausted** - Server at capacity
332
- - **deadline_exceeded** - Request timeout
333
- - **inactivity_timeout** - Session inactive for too long
334
- - **connection** - WebSocket connection failed
335
-
336
- ## Framework Integration Examples
337
-
338
- ### React/Preact Example
339
- ```jsx
340
- import { useEffect, useRef, useState } from "react";
341
- import { Conversation, EventType } from "@unith-ai/core-client";
342
-
343
- function DigitalHuman() {
344
- const videoRef = useRef(null);
345
- const conversationRef = useRef(null);
346
- const [status, setStatus] = useState("disconnected");
347
- const [messages, setMessages] = useState([]);
348
-
349
- useEffect(() => {
350
- const startConversation = async () => {
351
- try {
352
- const conversation = await Conversation.startDigitalHuman({
353
- orgId: "your-org-id",
354
- headId: "your-head-id",
355
- element: videoRef.current,
356
- onStatusChange: ({ status }) => setStatus(status),
357
- onText: (event) => {
358
- setMessages((prev) => [...prev, event]);
359
- },
360
- onResponse: (event) => {
361
- setMessages((prev) => [...prev, event]);
362
- },
363
- });
364
-
365
- conversationRef.current = conversation;
366
- await conversation.startSession();
367
- } catch (error) {
368
- console.error("Failed to start:", error);
369
- }
370
- };
371
-
372
- startConversation();
373
-
374
- return () => {
375
- conversationRef.current?.endSession();
376
- };
377
- }, []);
378
-
379
- const sendMessage = async (text) => {
380
- if (!conversationRef.current) return;
381
-
382
- await conversationRef.current.sendMessage({
383
- id: messages.length,
384
- timestamp: new Date().toISOString(),
385
- speaker: "user",
386
- text,
387
- isSent: false,
388
- user_id: conversationRef.current.getUserId(),
389
- username: "User",
390
- event: EventType.TEXT,
391
- visible: true,
392
- });
393
- };
394
-
395
- return (
396
- <div>
397
- <div ref={videoRef} style={{ width: "100%", height: "600px" }} />
398
- <div>Status: {status}</div>
399
- <button onClick={() => sendMessage("Hello!")}>Send Message</button>
400
- </div>
401
- );
402
- }
403
- ```
404
-
405
210
  ## TypeScript Support
406
211
 
407
212
  Full TypeScript types are included:
408
- ```typescript
409
- import {
410
- Conversation,
411
- EventType,
412
- Status,
413
- HeadType,
414
- VideoTransitionType,
415
- type ConversationOptions,
416
- type Message,
417
- type IncomingSocketEvent,
418
- } from "@unith-ai/core-client";
419
- ```
420
213
 
421
214
  ## Development
422
215
 
423
216
  Please refer to the README.md file in the root of this repository.
424
-
425
- ## Contributing
426
-
427
- Please create an issue first to discuss proposed changes. Any contributions are welcome!
428
-
429
- Remember, if merged, your code will be used as part of a MIT licensed project. By submitting a Pull Request, you are giving your consent for your code to be integrated into this library.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unith-ai/core-client",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "",
5
5
  "main": "./dist/lib.umd.js",
6
6
  "module": "./dist/lib.module.js",
package/dist/index.d.ts DELETED
@@ -1,91 +0,0 @@
1
- import { AudioOutput } from "./modules/audio";
2
- import { AVController } from "./modules/av";
3
- import { Connection } from "./modules/connection";
4
- import { IdleVideo } from "./modules/idle-video";
5
- import { SyncController } from "./modules/sync";
6
- import { User } from "./modules/user";
7
- import { VideoOutput } from "./modules/video";
8
- import { ResultType, STATUS_TYPES } from "./types/audio";
9
- import type { ConversationOptions, ConversationEvents, DigitalHumanOptions, HeadOptions, VideoHtmlElement } from "./types/Conversation";
10
- import { Environment } from "./types/environment";
11
- import { Message } from "./types/event";
12
- import { HeadType } from "./types/User";
13
- export * from "./types/event";
14
- export * from "./types/Conversation";
15
- export * from "./types/connection";
16
- export * from "./types/User";
17
- export * from "./types/environment";
18
- export { VideoTransitionType } from "./types/vp8";
19
- export type Options = ConversationOptions & ConversationEvents;
20
- export type HeadConfigOptions = HeadOptions & Partial<DigitalHumanOptions>;
21
- export type PartialOptions = HeadConfigOptions & VideoHtmlElement & Partial<ConversationEvents>;
22
- export declare class Conversation {
23
- options: Options;
24
- microphoneAccess: boolean;
25
- connection: Connection;
26
- idleVideo: IdleVideo;
27
- wakeLock: WakeLockSentinel | null;
28
- user: User;
29
- audioOutput: AudioOutput;
30
- videoOutput: VideoOutput;
31
- headInfo: HeadType;
32
- private status;
33
- protected volume: number;
34
- private sessionStarted;
35
- syncController: SyncController;
36
- avController: AVController;
37
- private monitor;
38
- private videoFrameQueue;
39
- private cachedResponseQueue;
40
- private static getFullOptions;
41
- /**
42
- * Starts a digital human conversation.
43
- * @param options - The options for the conversation.
44
- * @returns A promise that resolves to a Conversation instance.
45
- */
46
- static startDigitalHuman(options: PartialOptions): Promise<Conversation>;
47
- /**
48
- * This retrieves the background video to use for widget mode & welcome screen
49
- * @param options PartialOptions
50
- * @returns Video link to use as widget background
51
- */
52
- static getBackgroundVideo(options: {
53
- orgId: string;
54
- headId: string;
55
- environment: Environment;
56
- }): Promise<string>;
57
- constructor(options: Options, microphoneAccess: boolean, connection: Connection, idleVideo: IdleVideo, wakeLock: WakeLockSentinel | null, user: User, audioOutput: AudioOutput, videoOutput: VideoOutput, headInfo: HeadType);
58
- private startLatencyMonitoring;
59
- private handleIOSSilentMode;
60
- private onOutputWorkletMessage;
61
- private handleJoinEvent;
62
- private handleTextEvent;
63
- private handleResponseEvent;
64
- private handleStreamError;
65
- private handleStreamingEvent;
66
- private handleVideoFrame;
67
- private handleBinaryData;
68
- private handleAudioFrame;
69
- private onMessage;
70
- private endSessionWithDetails;
71
- getUserId(): string;
72
- private handleEndSession;
73
- private updateStatus;
74
- /**
75
- * To stop current response, we'll do the following:
76
- * 1. set a flag that'll prevent adding new audio & video events to their respective queues
77
- * 2. clear video queue & switch state to idle
78
- * 3. clear audio queue
79
- * 4. send an event if all expected response from BE has been received. If not, FE will keep status in 'stopping' mode
80
- */
81
- stopCurrentResponse(): void;
82
- toggleMuteStatus(): Promise<number>;
83
- startSession(): Promise<Connection>;
84
- initializeMicrophone(onSpeechResult: (result: ResultType) => void, onError: (e: string) => void, onStatusChange: (status: STATUS_TYPES) => void): Promise<{
85
- token: string | null | undefined;
86
- region: string | null | undefined;
87
- }>;
88
- endSession(): Promise<void>;
89
- sendMessage(message: Message): void;
90
- keepSession(message: Message): void;
91
- }