@voqalize/avatar 0.5.4 → 0.5.6

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
@@ -71,6 +71,38 @@ The gains are optional, from `0` to `2`, and `1` is as authored:
71
71
 
72
72
  The client must be connected to a Voqalize session that has the avatar turned on.
73
73
 
74
+ ## Preload before the call
75
+
76
+ Building a face takes the browser a moment: the runtime, the character, a GPU
77
+ context and its shaders. On a slow phone that is over a second after the call
78
+ connects before the face appears. `preloadAvatar` does that work before the call, off screen, so the
79
+ mount that follows only moves the finished face onto the page:
80
+
81
+ ```js
82
+ import { preloadAvatar, unloadAvatar } from "@voqalize/avatar";
83
+
84
+ // when the visitor is about to talk: the page with the call button, or hover on it
85
+ const face = preloadAvatar("<name>");
86
+
87
+ // later, the ordinary mount takes the preloaded face
88
+ createAvatar({ mount, client, character: "<name>" });
89
+
90
+ // or, if the visitor leaves without calling
91
+ unloadAvatar(face);
92
+ ```
93
+
94
+ - One face is held at a time. Preloading the same character again returns the
95
+ same handle; preloading another frees the one held.
96
+ - A mount of that character, through `createAvatar` or `<Avatar>`, takes the
97
+ face. After that the handle holds nothing and `unloadAvatar` does nothing,
98
+ and `destroy()` frees the face as usual.
99
+ - `face.ready` settles when the face is built, or when it cannot be (no
100
+ WebGL 2, for example) and the mount will build it the ordinary way. You
101
+ never need to wait for it before mounting. It rejects only for a name that is
102
+ no character.
103
+ - `unloadAvatar` frees the GPU context and the character's memory at once,
104
+ and is safe to call at any time.
105
+
74
106
  ### React
75
107
 
76
108
  ```jsx
package/dist/index.d.ts CHANGED
@@ -9,6 +9,10 @@
9
9
  *
10
10
  * const roster = await listCharacters(); // every character, to choose from
11
11
  *
12
+ * const face = preloadAvatar("tara"); // before the call: build it off screen
13
+ * await face.ready; // optional; createAvatar takes it either way
14
+ * unloadAvatar(face); // the visitor left without calling
15
+ *
12
16
  * This package is the loader and nothing else. The avatar itself — the
13
17
  * characters, the renderer and everything that reads the call — is the
14
18
  * Voqalize avatar runtime, which this module imports from
@@ -67,6 +71,14 @@ export interface CharacterInfo {
67
71
  * for example. Advice only: the avatar never speaks. */
68
72
  readonly suggestedVoices: readonly string[];
69
73
  }
74
+ /** A face `preloadAvatar` is building, or has built, ahead of its mount. */
75
+ export interface PreloadedAvatar {
76
+ /** The character it is. */
77
+ readonly character: string;
78
+ /** Settles once the face is built, or once it cannot be and the mount will
79
+ * build it the ordinary way. Rejects only for a name that is no character. */
80
+ readonly ready: Promise<void>;
81
+ }
70
82
  export declare function createAvatar(options: AvatarOptions): AvatarInstance;
71
83
  /**
72
84
  * Every character the runtime can mount, in a stable order — for a page that
@@ -75,3 +87,26 @@ export declare function createAvatar(options: AvatarOptions): AvatarInstance;
75
87
  * runtime's own and cannot be changed.
76
88
  */
77
89
  export declare function listCharacters(): Promise<readonly CharacterInfo[]>;
90
+ /**
91
+ * Build a character's face before there is anywhere to show it — the runtime,
92
+ * the character, its GPU context, its shaders and a first frame — so that a
93
+ * `createAvatar` (or `<Avatar>`) of the same character later only moves it onto
94
+ * the page. Call it when the visitor shows they are about to talk: on the page
95
+ * that holds the call button, or on hover over it.
96
+ *
97
+ * Holds one face. The same character again returns the same handle; another
98
+ * character frees the one held and builds the new one. A mount of the
99
+ * character takes the face, and the handle no longer holds anything. Nothing
100
+ * else changes for a mount: one with no preload, or of another character,
101
+ * builds its face as it always has.
102
+ */
103
+ export declare function preloadAvatar(character: string): PreloadedAvatar;
104
+ /**
105
+ * Free the face `preloadAvatar` built: its GPU context, the decoded character
106
+ * and its textures. For a visitor who leaves without calling, or a page that
107
+ * will not mount it after all. A handle a mount has taken, or that a preload
108
+ * of another character has already replaced, holds nothing, and unloading it
109
+ * does nothing. Safe to call at any time, including before `ready` settles;
110
+ * it then settles without the face being built, or with the build abandoned.
111
+ */
112
+ export declare function unloadAvatar(face: PreloadedAvatar): void;
package/dist/index.js CHANGED
@@ -9,6 +9,10 @@
9
9
  *
10
10
  * const roster = await listCharacters(); // every character, to choose from
11
11
  *
12
+ * const face = preloadAvatar("tara"); // before the call: build it off screen
13
+ * await face.ready; // optional; createAvatar takes it either way
14
+ * unloadAvatar(face); // the visitor left without calling
15
+ *
12
16
  * This package is the loader and nothing else. The avatar itself — the
13
17
  * characters, the renderer and everything that reads the call — is the
14
18
  * Voqalize avatar runtime, which this module imports from
@@ -44,8 +48,13 @@ export function createAvatar(options) {
44
48
  let destroyed = false;
45
49
  load()
46
50
  .then((rt) => {
47
- if (!destroyed)
48
- instance = rt.mount({ ...options, protocol: PROTOCOL });
51
+ if (destroyed)
52
+ return;
53
+ // The runtime moves a preloaded face of this character into the mount,
54
+ // after which it is the avatar's to destroy and no handle's to unload.
55
+ if (held?.character === options.character)
56
+ held = undefined;
57
+ instance = rt.mount({ ...options, protocol: PROTOCOL });
49
58
  })
50
59
  .catch((error) => console.error(`[avatar] could not start the avatar from ${RUNTIME_URL}`, error));
51
60
  return {
@@ -68,3 +77,59 @@ export async function listCharacters() {
68
77
  throw new Error(`[avatar] the runtime at ${RUNTIME_URL} does not list its characters`);
69
78
  return rt.characters();
70
79
  }
80
+ // The one face built ahead of its mount. One, not one per character: each
81
+ // holds a GPU context and a decoded head, and a page about to call knows
82
+ // which character it is about to show.
83
+ let held;
84
+ /**
85
+ * Build a character's face before there is anywhere to show it — the runtime,
86
+ * the character, its GPU context, its shaders and a first frame — so that a
87
+ * `createAvatar` (or `<Avatar>`) of the same character later only moves it onto
88
+ * the page. Call it when the visitor shows they are about to talk: on the page
89
+ * that holds the call button, or on hover over it.
90
+ *
91
+ * Holds one face. The same character again returns the same handle; another
92
+ * character frees the one held and builds the new one. A mount of the
93
+ * character takes the face, and the handle no longer holds anything. Nothing
94
+ * else changes for a mount: one with no preload, or of another character,
95
+ * builds its face as it always has.
96
+ */
97
+ export function preloadAvatar(character) {
98
+ if (!character)
99
+ throw new TypeError("preloadAvatar: `character` is required");
100
+ if (held?.character === character)
101
+ return held;
102
+ if (held)
103
+ unloadAvatar(held);
104
+ const face = {
105
+ character,
106
+ ready: load().then((rt) => (held === face ? rt.preload?.(character) : undefined),
107
+ // A runtime that will not load leaves the mount to try again and report
108
+ // it; a preload is never the reason a page cannot call.
109
+ (error) => {
110
+ if (held === face)
111
+ held = undefined;
112
+ console.error(`[avatar] could not preload the avatar from ${RUNTIME_URL}`, error);
113
+ }).then(() => undefined, (error) => {
114
+ if (held === face)
115
+ held = undefined;
116
+ throw error;
117
+ }),
118
+ };
119
+ held = face;
120
+ return face;
121
+ }
122
+ /**
123
+ * Free the face `preloadAvatar` built: its GPU context, the decoded character
124
+ * and its textures. For a visitor who leaves without calling, or a page that
125
+ * will not mount it after all. A handle a mount has taken, or that a preload
126
+ * of another character has already replaced, holds nothing, and unloading it
127
+ * does nothing. Safe to call at any time, including before `ready` settles;
128
+ * it then settles without the face being built, or with the build abandoned.
129
+ */
130
+ export function unloadAvatar(face) {
131
+ if (!face || held !== face)
132
+ return;
133
+ held = undefined;
134
+ load().then((rt) => rt.unload?.(face.character), () => { });
135
+ }
package/dist/pin.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const RUNTIME_URL = "https://avatar.voqalize.com/runtime/runtime.WZVOTQJM.js";
1
+ export declare const RUNTIME_URL = "https://avatar.voqalize.com/runtime/runtime.2X4UMJ6T.js";
2
2
  export declare const PROTOCOL = 1;
package/dist/pin.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Written by scripts/build-runtime.mjs — the runtime this shim is released with.
2
- export const RUNTIME_URL = "https://avatar.voqalize.com/runtime/runtime.WZVOTQJM.js";
2
+ export const RUNTIME_URL = "https://avatar.voqalize.com/runtime/runtime.2X4UMJ6T.js";
3
3
  export const PROTOCOL = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voqalize/avatar",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "A talking head for AI voice calls that embodies your pipecat client. This package loads the Voqalize avatar runtime.",
5
5
  "license": "MIT",
6
6
  "author": "Voqalize",