@convai/web-widget 0.2.1 → 0.3.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/README.md CHANGED
@@ -13,7 +13,7 @@ production** — [step two](#before-you-ship) moves it to your server.
13
13
 
14
14
  ```html
15
15
  <convai-widget character-id="YOUR_CHARACTER_ID" api-key="YOUR_API_KEY"></convai-widget>
16
- <script src="https://cdn.jsdelivr.net/npm/@convai/web-widget@0.2.1/v1.js" async></script>
16
+ <script src="https://cdn.jsdelivr.net/npm/@convai/web-widget@0.3.1/v1.js" async></script>
17
17
  ```
18
18
 
19
19
  A launcher bubble appears in the bottom-right corner. Click it: the runtime
@@ -26,7 +26,7 @@ The script tag goes in your HTML shell; the element is ordinary JSX.
26
26
 
27
27
  ```html
28
28
  <!-- index.html -->
29
- <script src="https://cdn.jsdelivr.net/npm/@convai/web-widget@0.2.1/v1.js" async></script>
29
+ <script src="https://cdn.jsdelivr.net/npm/@convai/web-widget@0.3.1/v1.js" async></script>
30
30
  ```
31
31
 
32
32
  ```jsx
@@ -65,6 +65,28 @@ export function Chat() {
65
65
  }
66
66
  ```
67
67
 
68
+ `convaiClient` takes any `ConvaiClient`, not only `useConvaiClient`'s: one you
69
+ built with `new ConvaiClient(...)`, or the character's own client from
70
+ `@convai/embody`. The widget subscribes to its events itself and never opens a
71
+ second connection.
72
+
73
+ ```tsx
74
+ import { useState } from 'react'
75
+ import type { ConvaiClient } from '@convai/web-sdk/core'
76
+ import { ConvaiCharacter } from '@convai/embody/react'
77
+ import { ConvaiWidget } from '@convai/web-widget/react'
78
+
79
+ export function CharacterWithChat() {
80
+ const [client, setClient] = useState<ConvaiClient | null>(null)
81
+ return (
82
+ <>
83
+ <ConvaiCharacter apiKey={API_KEY} characterId={CHARACTER_ID} onReady={(c) => setClient(c.client)} />
84
+ {client && <ConvaiWidget convaiClient={client} />}
85
+ </>
86
+ )
87
+ }
88
+ ```
89
+
68
90
  **No framework**
69
91
 
70
92
  ```ts
@@ -145,16 +167,16 @@ guide; theming and `::part()` hooks; CSP requirements; and guidance on
145
167
 
146
168
  ## Versioning
147
169
 
148
- `0.2.1` is a stable release: `npm install @convai/web-widget` and
170
+ `0.3.1` is a stable release: `npm install @convai/web-widget` and
149
171
  jsDelivr's unversioned URL both resolve it. Pin the exact version anyway for
150
172
  reproducible deploys —
151
173
 
152
174
  ```html
153
- <script src="https://cdn.jsdelivr.net/npm/@convai/web-widget@0.2.1/v1.js" async></script>
175
+ <script src="https://cdn.jsdelivr.net/npm/@convai/web-widget@0.3.1/v1.js" async></script>
154
176
  ```
155
177
 
156
178
  ```bash
157
- npm install @convai/web-widget@0.2.1 # only needed to self-host the two files
179
+ npm install @convai/web-widget@0.3.1 # only needed to self-host the two files
158
180
  ```
159
181
 
160
182
  This package is early, and it bundles a prerelease of `@convai/web-sdk`. The
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const VERSION = "0.2.1";
1
+ const VERSION = "0.3.1";
2
2
  export {
3
3
  VERSION
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@convai/web-widget",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Two-line script-tag embed for the Convai chat widget. A <convai-widget> custom element with a closed shadow root and a lazily loaded runtime.",
5
5
  "homepage": "https://docs.convai.com/embed",
6
6
  "repository": {
package/react.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
2
+ import React, { useMemo, useReducer, useEffect, useState, useRef, useCallback } from "react";
3
3
  import { motion, AnimatePresence } from "framer-motion";
4
4
  import { useLocalParticipant, useRoomContext, RoomContext, VideoTrack } from "@livekit/components-react";
5
5
  import { RoomContext as RoomContext2, RoomAudioRenderer } from "@livekit/components-react";
@@ -9,6 +9,91 @@ import { freshPublicationGrantOptions } from "@convai/web-sdk/embed";
9
9
  import styled from "styled-components";
10
10
  import { p as presentationStyles, M as MAX_CARDS_PER_MESSAGE, i as injectKeyframes, P as PREVIEW_TIMEOUT_MS, g as glyph, o as openLightbox } from "./chunks/lightbox-Dxfdw3VU.js";
11
11
  import { Track } from "livekit-client";
12
+ function listen(target, event, fn) {
13
+ if (!(target == null ? void 0 : target.on)) return () => {
14
+ };
15
+ const result = target.on(event, fn);
16
+ if (typeof result === "function") return result;
17
+ return () => {
18
+ var _a;
19
+ return (_a = target.off) == null ? void 0 : _a.call(target, event, fn);
20
+ };
21
+ }
22
+ function activityOf(state) {
23
+ if (!state) return "Idle";
24
+ if (state.isSpeaking) return "Speaking";
25
+ if (state.isListening) return "Listening";
26
+ if (state.isThinking) return "Thinking";
27
+ if (state.isConnected) return "Connected";
28
+ return "Idle";
29
+ }
30
+ function isManagedClient(client) {
31
+ return typeof client.isAudioMuted === "boolean";
32
+ }
33
+ function useLiveClientView(client) {
34
+ const { fields, version, managed } = useLiveClientState(client);
35
+ return useMemo(() => {
36
+ if (managed) return client;
37
+ return new Proxy(client, {
38
+ get(target, prop, receiver) {
39
+ if (typeof prop === "string" && prop in fields) return fields[prop];
40
+ const value = Reflect.get(target, prop, target);
41
+ return typeof value === "function" ? value.bind(target) : value;
42
+ },
43
+ has(target, prop) {
44
+ return typeof prop === "string" && prop in fields || Reflect.has(target, prop);
45
+ }
46
+ });
47
+ }, [client, managed, version]);
48
+ }
49
+ function useLiveClientState(client) {
50
+ var _a, _b, _c, _d, _e, _f, _g;
51
+ const managed = isManagedClient(client);
52
+ const [version, rerender] = useReducer((n) => n + 1, 0);
53
+ useEffect(() => {
54
+ if (managed) return;
55
+ const c = client;
56
+ const unsubs = [
57
+ ...[
58
+ "stateChange",
59
+ "messagesChange",
60
+ "userTranscriptionChange",
61
+ "speakingChange",
62
+ "botReady",
63
+ "connect",
64
+ "disconnect"
65
+ ].map((event) => listen(c, event, rerender)),
66
+ listen(c.audioControls, "audioStateChange", rerender),
67
+ listen(c.videoControls, "videoStateChange", rerender),
68
+ listen(c.screenShareControls, "screenShareStateChange", rerender)
69
+ ];
70
+ rerender();
71
+ return () => unsubs.forEach((unsubscribe) => unsubscribe());
72
+ }, [client, managed]);
73
+ if (managed) {
74
+ return {
75
+ managed,
76
+ version,
77
+ fields: {
78
+ activity: (_a = client.activity) != null ? _a : activityOf(client.state),
79
+ isAudioMuted: client.isAudioMuted,
80
+ isVideoEnabled: Boolean(client.isVideoEnabled),
81
+ isScreenShareActive: Boolean(client.isScreenShareActive)
82
+ }
83
+ };
84
+ }
85
+ const controls = client;
86
+ return {
87
+ managed,
88
+ version,
89
+ fields: {
90
+ activity: activityOf(client.state),
91
+ isAudioMuted: (_c = (_b = controls.audioControls) == null ? void 0 : _b.isAudioMuted) != null ? _c : true,
92
+ isVideoEnabled: (_e = (_d = controls.videoControls) == null ? void 0 : _d.isVideoEnabled) != null ? _e : false,
93
+ isScreenShareActive: (_g = (_f = controls.screenShareControls) == null ? void 0 : _f.isScreenShareActive) != null ? _g : false
94
+ }
95
+ };
96
+ }
12
97
  function renderTree(tree) {
13
98
  return tree.map(
14
99
  (node, i) => React.createElement(node.tag, { key: i, ...node.attr }, renderTree(node.child))
@@ -2702,7 +2787,7 @@ const VoiceModeOverlay = ({
2702
2787
  );
2703
2788
  };
2704
2789
  const ConvaiWidget = ({
2705
- convaiClient,
2790
+ convaiClient: givenClient,
2706
2791
  showVideo = true,
2707
2792
  showScreenShare = true,
2708
2793
  defaultVoiceMode = true,
@@ -2712,6 +2797,7 @@ const ConvaiWidget = ({
2712
2797
  linkPreviewResolver
2713
2798
  }) => {
2714
2799
  var _a, _b, _c, _d;
2800
+ const convaiClient = useLiveClientView(givenClient);
2715
2801
  const isTextOnly = Boolean(requestPublicationGrant) || convaiClient.connectionType === "text";
2716
2802
  const {
2717
2803
  name: fetchedCharacterName,