@nuancelabs/js-sdk 0.1.1 → 0.2.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.
Files changed (3) hide show
  1. package/index.d.ts +24 -4
  2. package/index.js +35 -2
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -157,10 +157,10 @@ export declare type ConversationEvent =
157
157
  type: "avatar-ready";
158
158
  }
159
159
  /**
160
- * The avatar is presented as she should be seen: her video has reached its
161
- * full size and held it, or the wait for it ran out. Follows `avatar-ready`.
162
- * The session opens on it: her first words, and its recording. Reveal her
163
- * fully on this; between the two she is in view but not yet speaking.
160
+ * The avatar starts speaking: her first words, and the session's recording.
161
+ * Follows `avatar-ready`, at once by default. With `startOn: "full-quality"`
162
+ * it waits until her video has reached its full size and held it, or the
163
+ * wait ran out; in between she is in view but not yet speaking.
164
164
  */
165
165
  | {
166
166
  type: "avatar-presented";
@@ -296,6 +296,9 @@ export declare type ConversationStateValue = `${ConversationState}`;
296
296
  */
297
297
  export declare function createNuanceConversation(options?: CreateNuanceConversationOptions): Conversation;
298
298
 
299
+ /**
300
+ * Options for creating a Nuance conversation.
301
+ */
299
302
  export declare interface CreateNuanceConversationOptions {
300
303
  /**
301
304
  * Fetches a session token, for authenticating from a web page.
@@ -324,6 +327,20 @@ export declare interface CreateNuanceConversationOptions {
324
327
  videoCapture?: VideoCaptureConstraints;
325
328
  /** Local capture preferences, resolved once for each connection attempt. */
326
329
  mediaDevices?: MediaDevicePreferences | (() => MediaDevicePreferences);
330
+ /**
331
+ * When the avatar starts speaking.
332
+ *
333
+ * - `"first-frame"`: as soon as her first frame reaches the page.
334
+ * `avatar-presented` follows `avatar-ready` at once. Her video may be
335
+ * soft for its first few seconds while the connection ramps up.
336
+ * - `"full-quality"`: once her video has reached its full size and held
337
+ * it, or after at most about 12 seconds. She is in view but silent in
338
+ * between, so a page using this should cover her until
339
+ * `avatar-presented`.
340
+ *
341
+ * @defaultValue `"first-frame"`
342
+ */
343
+ startOn?: StartOn;
327
344
  }
328
345
 
329
346
 
@@ -412,6 +429,9 @@ export declare type SessionEndReason = "time-limit" | "ended" | "policy-violatio
412
429
 
413
430
 
414
431
 
432
+ /** When the avatar starts speaking: see {@link CreateNuanceConversationOptions.startOn}. */
433
+ export declare type StartOn = "first-frame" | "full-quality";
434
+
415
435
 
416
436
  /**
417
437
  * The local user participant in a conversation.
package/index.js CHANGED
@@ -4,7 +4,7 @@ function isRecord(value) {
4
4
  }
5
5
 
6
6
  // src/core/sdk-version.ts
7
- var SDK_HEADER = `js/${"0.1.1"}`;
7
+ var SDK_HEADER = `js/${"0.2.0"}`;
8
8
 
9
9
  // src/utils.ts
10
10
  function normalizeHost(host) {
@@ -1078,6 +1078,9 @@ function parseSessionEvent(payload) {
1078
1078
  }
1079
1079
  };
1080
1080
  }
1081
+ if (decoded.type === "floor") {
1082
+ return { type: "floor", speaking: decoded.state === "speaking" };
1083
+ }
1081
1084
  if (decoded.type === "session.status" && decoded.status === "ended") {
1082
1085
  return {
1083
1086
  type: "ended",
@@ -1520,6 +1523,7 @@ class LiveKitConversation {
1520
1523
  eventsRegistered = false;
1521
1524
  sessionEnded = false;
1522
1525
  speaksProtocol = false;
1526
+ avatarSpeaking = false;
1523
1527
  answered = null;
1524
1528
  commands = new Map;
1525
1529
  commandCount = 0;
@@ -1665,6 +1669,12 @@ class LiveKitConversation {
1665
1669
  }
1666
1670
  return this.command({ type: "scene.background", background });
1667
1671
  }
1672
+ async sendText(text) {
1673
+ const line = text.trim();
1674
+ if (!line)
1675
+ throw new Error("Nothing to send: the text is blank");
1676
+ return this.command({ type: "input.text", text: line, respond: true });
1677
+ }
1668
1678
  onChange(handler) {
1669
1679
  return this.onEvent((event) => {
1670
1680
  if (event.type !== "data-event")
@@ -1748,6 +1758,8 @@ class LiveKitConversation {
1748
1758
  const mapped = mapConnectionState(lkState, this.resilient && this.everConnected);
1749
1759
  if (mapped === "connected")
1750
1760
  this.everConnected = true;
1761
+ if (lkState === ConnectionState2.Reconnecting)
1762
+ this.setAvatarSpeaking(false);
1751
1763
  if (mapped !== this._state) {
1752
1764
  this.setState(mapped);
1753
1765
  }
@@ -1787,6 +1799,7 @@ class LiveKitConversation {
1787
1799
  this.room.on(RoomEvent3.Disconnected, () => {
1788
1800
  this.stats?.abandon();
1789
1801
  this.stats = null;
1802
+ this.setAvatarSpeaking(false);
1790
1803
  this.stopPresentation();
1791
1804
  this.stopReadyFallback();
1792
1805
  this.stopLayerFallback();
@@ -1902,6 +1915,8 @@ class LiveKitConversation {
1902
1915
  this.commands.delete(id);
1903
1916
  settle({ status: "rejected", error: `unknown command ${type}` });
1904
1917
  }
1918
+ } else if (event.type === "floor") {
1919
+ this.setAvatarSpeaking(event.speaking);
1905
1920
  } else if (event.type === "ended") {
1906
1921
  this.answered?.();
1907
1922
  if (!event.byClient)
@@ -1934,6 +1949,16 @@ class LiveKitConversation {
1934
1949
  this.readyFallback?.stop();
1935
1950
  this.readyFallback = null;
1936
1951
  }
1952
+ setAvatarSpeaking(speaking) {
1953
+ if (speaking === this.avatarSpeaking)
1954
+ return;
1955
+ this.avatarSpeaking = speaking;
1956
+ this.emit({
1957
+ type: "speaking-changed",
1958
+ participant: "avatar",
1959
+ isSpeaking: speaking
1960
+ });
1961
+ }
1937
1962
  markAvatarReady() {
1938
1963
  this.stopReadyFallback();
1939
1964
  if (this._avatar.isReady)
@@ -2116,6 +2141,7 @@ class NuanceConversation {
2116
2141
  personality;
2117
2142
  variant;
2118
2143
  videoCapture;
2144
+ startOn;
2119
2145
  mediaDevices;
2120
2146
  resilientReconnect;
2121
2147
  delegate = null;
@@ -2151,6 +2177,7 @@ class NuanceConversation {
2151
2177
  this.personality = options.personality;
2152
2178
  this.variant = options.variant;
2153
2179
  this.videoCapture = options.videoCapture;
2180
+ this.startOn = options.startOn ?? "first-frame";
2154
2181
  this.mediaDevices = options.mediaDevices;
2155
2182
  this.resilientReconnect = options.resilientReconnect ?? false;
2156
2183
  }
@@ -2176,7 +2203,7 @@ class NuanceConversation {
2176
2203
  try {
2177
2204
  const request = {
2178
2205
  session_overrides: this.sessionOverrides,
2179
- client_ready: true
2206
+ client_ready: this.startOn === "full-quality"
2180
2207
  };
2181
2208
  const timezone = this.timezone();
2182
2209
  if (timezone)
@@ -2281,6 +2308,12 @@ class NuanceConversation {
2281
2308
  }
2282
2309
  return this.delegate.setBackground(background);
2283
2310
  }
2311
+ async sendText(text) {
2312
+ if (!this.delegate) {
2313
+ throw new Error("Not connected — call connect() first");
2314
+ }
2315
+ return this.delegate.sendText(text);
2316
+ }
2284
2317
  onChange(handler) {
2285
2318
  return this.onEvent((event) => {
2286
2319
  if (event.type !== "data-event")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuancelabs/js-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "JavaScript SDK for Nuance Labs conversation video AI avatar",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {