@convai/web-sdk 1.8.0-beta.5 → 1.8.0-beta.7
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 +333 -13
- package/dist/core/CharacterRoster.d.ts +84 -0
- package/dist/core/CharacterRoster.d.ts.map +1 -0
- package/dist/core/CharacterRoster.js +348 -0
- package/dist/core/CharacterRoster.js.map +1 -0
- package/dist/core/ConvaiClient.d.ts +152 -2
- package/dist/core/ConvaiClient.d.ts.map +1 -1
- package/dist/core/ConvaiClient.js +1036 -49
- package/dist/core/ConvaiClient.js.map +1 -1
- package/dist/core/ConvaiRoomError.d.ts +48 -0
- package/dist/core/ConvaiRoomError.d.ts.map +1 -0
- package/dist/core/ConvaiRoomError.js +111 -0
- package/dist/core/ConvaiRoomError.js.map +1 -0
- package/dist/core/MessageHandler.d.ts +25 -0
- package/dist/core/MessageHandler.d.ts.map +1 -1
- package/dist/core/MessageHandler.js +431 -30
- package/dist/core/MessageHandler.js.map +1 -1
- package/dist/core/SSESession.d.ts +15 -0
- package/dist/core/SSESession.d.ts.map +1 -1
- package/dist/core/SSESession.js +92 -3
- package/dist/core/SSESession.js.map +1 -1
- package/dist/core/connectRequest.d.ts +38 -2
- package/dist/core/connectRequest.d.ts.map +1 -1
- package/dist/core/connectRequest.js +57 -0
- package/dist/core/connectRequest.js.map +1 -1
- package/dist/core/index.d.ts +6 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/logicalTurn.d.ts +4 -1
- package/dist/core/logicalTurn.d.ts.map +1 -1
- package/dist/core/logicalTurn.js +35 -4
- package/dist/core/logicalTurn.js.map +1 -1
- package/dist/core/modelOutput.d.ts +40 -0
- package/dist/core/modelOutput.d.ts.map +1 -0
- package/dist/core/modelOutput.js +187 -0
- package/dist/core/modelOutput.js.map +1 -0
- package/dist/core/publishedChat.d.ts +9 -0
- package/dist/core/publishedChat.d.ts.map +1 -0
- package/dist/core/publishedChat.js +13 -0
- package/dist/core/publishedChat.js.map +1 -0
- package/dist/core/rosterRequest.d.ts +57 -0
- package/dist/core/rosterRequest.d.ts.map +1 -0
- package/dist/core/rosterRequest.js +210 -0
- package/dist/core/rosterRequest.js.map +1 -0
- package/dist/core/types.d.ts +667 -10
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/embed/ConvaiChatElement.d.ts +27 -0
- package/dist/embed/ConvaiChatElement.d.ts.map +1 -0
- package/dist/embed/ConvaiChatElement.js +300 -0
- package/dist/embed/ConvaiChatElement.js.map +1 -0
- package/dist/embed/browser.d.ts +3 -0
- package/dist/embed/browser.d.ts.map +1 -0
- package/dist/embed/browser.js +5 -0
- package/dist/embed/browser.js.map +1 -0
- package/dist/embed/chat-embed-v1.js +100 -0
- package/dist/embed/chat-embed-v1.js.map +7 -0
- package/dist/embed/index.d.ts +9 -0
- package/dist/embed/index.d.ts.map +1 -0
- package/dist/embed/index.js +9 -0
- package/dist/embed/index.js.map +1 -0
- package/dist/embed/publicationApi.d.ts +23 -0
- package/dist/embed/publicationApi.d.ts.map +1 -0
- package/dist/embed/publicationApi.js +141 -0
- package/dist/embed/publicationApi.js.map +1 -0
- package/dist/react/components/ConvaiWidget.d.ts +11 -1
- package/dist/react/components/ConvaiWidget.d.ts.map +1 -1
- package/dist/react/components/ConvaiWidget.js +67 -11
- package/dist/react/components/ConvaiWidget.js.map +1 -1
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.d.ts.map +1 -1
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.js +39 -44
- package/dist/react/components/rtc-widget/components/MarkdownRenderer.js.map +1 -1
- package/dist/react/components/rtc-widget/components/conviComponents/SettingsTray.d.ts +1 -1
- package/dist/react/components/rtc-widget/components/conviComponents/SettingsTray.d.ts.map +1 -1
- package/dist/react/components/rtc-widget/components/conviComponents/SettingsTray.js +2 -2
- package/dist/react/components/rtc-widget/components/conviComponents/SettingsTray.js.map +1 -1
- package/dist/react/hooks/useConvaiClient.d.ts +2 -0
- package/dist/react/hooks/useConvaiClient.d.ts.map +1 -1
- package/dist/react/hooks/useConvaiClient.js +16 -0
- package/dist/react/hooks/useConvaiClient.js.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/dist/utils/inlineMarkdown.d.ts +38 -0
- package/dist/utils/inlineMarkdown.d.ts.map +1 -0
- package/dist/utils/inlineMarkdown.js +106 -0
- package/dist/utils/inlineMarkdown.js.map +1 -0
- package/dist/vanilla/AudioRenderer.d.ts.map +1 -1
- package/dist/vanilla/AudioRenderer.js +6 -0
- package/dist/vanilla/AudioRenderer.js.map +1 -1
- package/dist/vanilla/ConvaiWidget.d.ts.map +1 -1
- package/dist/vanilla/ConvaiWidget.js +224 -89
- package/dist/vanilla/ConvaiWidget.js.map +1 -1
- package/dist/vanilla/WidgetEventSubscriptions.d.ts +15 -0
- package/dist/vanilla/WidgetEventSubscriptions.d.ts.map +1 -0
- package/dist/vanilla/WidgetEventSubscriptions.js +37 -0
- package/dist/vanilla/WidgetEventSubscriptions.js.map +1 -0
- package/dist/vanilla/index.d.ts +2 -1
- package/dist/vanilla/index.d.ts.map +1 -1
- package/dist/vanilla/index.js +2 -1
- package/dist/vanilla/index.js.map +1 -1
- package/dist/vanilla/types.d.ts +8 -1
- package/dist/vanilla/types.d.ts.map +1 -1
- package/dist/vanilla/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +19 -6
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Published-chat descriptor and grant helpers. Server callers must provide a
|
|
3
|
+
* caller-scoped requester ID when minting a grant.
|
|
4
|
+
*
|
|
5
|
+
* Import `@convai/web-sdk/embed/browser` in a browser to register the
|
|
6
|
+
* `<convai-chat>` custom element.
|
|
7
|
+
*/
|
|
8
|
+
export * from "./publicationApi.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/embed/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Published-chat descriptor and grant helpers. Server callers must provide a
|
|
3
|
+
* caller-scoped requester ID when minting a grant.
|
|
4
|
+
*
|
|
5
|
+
* Import `@convai/web-sdk/embed/browser` in a browser to register the
|
|
6
|
+
* `<convai-chat>` custom element.
|
|
7
|
+
*/
|
|
8
|
+
export * from "./publicationApi.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/embed/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,qBAAqB,CAAC","sourcesContent":["/**\n * Published-chat descriptor and grant helpers. Server callers must provide a\n * caller-scoped requester ID when minting a grant.\n *\n * Import `@convai/web-sdk/embed/browser` in a browser to register the\n * `<convai-chat>` custom element.\n */\nexport * from \"./publicationApi.js\";\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface PublishedChatDescriptor {
|
|
2
|
+
publication_id: string;
|
|
3
|
+
display_name: string;
|
|
4
|
+
description: string | null;
|
|
5
|
+
image_url: string | null;
|
|
6
|
+
mode: "text";
|
|
7
|
+
}
|
|
8
|
+
export type PublishedChatFetch = typeof fetch;
|
|
9
|
+
export declare const DEFAULT_PUBLICATION_API_URL = "https://api.convai.com";
|
|
10
|
+
export interface PublishedChatEndpoints {
|
|
11
|
+
descriptorUrl: URL;
|
|
12
|
+
launchGrantUrl: URL;
|
|
13
|
+
}
|
|
14
|
+
export interface PublishedChatSession {
|
|
15
|
+
descriptor: PublishedChatDescriptor;
|
|
16
|
+
launchToken: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function parsePublishedChatUrl(value: string, name: string): URL;
|
|
19
|
+
export declare function getPublishedChatEndpoints(publicationId: string, publicationApiUrl?: string): PublishedChatEndpoints;
|
|
20
|
+
export declare function fetchPublishedChatDescriptor(descriptorUrl: string, fetchImpl?: PublishedChatFetch, signal?: AbortSignal): Promise<PublishedChatDescriptor>;
|
|
21
|
+
export declare function requestPublishedChatGrant(launchGrantUrl: string, fetchImpl?: PublishedChatFetch, signal?: AbortSignal, requesterId?: string): Promise<string>;
|
|
22
|
+
export declare function loadPublishedChatSession(publicationId: string, publicationApiUrl?: string, fetchImpl?: PublishedChatFetch, signal?: AbortSignal, requesterId?: string): Promise<PublishedChatSession>;
|
|
23
|
+
//# sourceMappingURL=publicationApi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publicationApi.d.ts","sourceRoot":"","sources":["../../src/embed/publicationApi.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,KAAK,CAAC;AAW9C,eAAO,MAAM,2BAA2B,2BAA2B,CAAC;AAEpE,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,GAAG,CAAC;IACnB,cAAc,EAAE,GAAG,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,uBAAuB,CAAC;IACpC,WAAW,EAAE,MAAM,CAAC;CACrB;AA2CD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,CActE;AAED,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,EACrB,iBAAiB,SAA8B,GAC9C,sBAAsB,CAYxB;AAUD,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,EACrB,SAAS,GAAE,kBAA0B,EACrC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,uBAAuB,CAAC,CAyBlC;AAED,wBAAsB,yBAAyB,CAC7C,cAAc,EAAE,MAAM,EACtB,SAAS,GAAE,kBAA0B,EACrC,MAAM,CAAC,EAAE,WAAW,EACpB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAED,wBAAsB,wBAAwB,CAC5C,aAAa,EAAE,MAAM,EACrB,iBAAiB,CAAC,EAAE,MAAM,EAC1B,SAAS,GAAE,kBAA0B,EACrC,MAAM,CAAC,EAAE,WAAW,EACpB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAoB/B"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const LAUNCH_TOKEN_MIN_LENGTH = 32;
|
|
2
|
+
const LAUNCH_TOKEN_MAX_LENGTH = 512;
|
|
3
|
+
const PUBLICATION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
|
+
const PUBLICATION_CLIENT_ID_RE = /^[A-Za-z0-9_-]{16,128}$/;
|
|
5
|
+
const PUBLICATION_CLIENT_ID_STORAGE_KEY = "convai.published-chat-client-id.v1";
|
|
6
|
+
let inMemoryPublicationClientId;
|
|
7
|
+
export const DEFAULT_PUBLICATION_API_URL = "https://api.convai.com";
|
|
8
|
+
function newPublicationClientId() {
|
|
9
|
+
const clientId = globalThis.crypto?.randomUUID?.();
|
|
10
|
+
if (!clientId) {
|
|
11
|
+
throw new Error("Published chat requires a secure browser random source");
|
|
12
|
+
}
|
|
13
|
+
return clientId;
|
|
14
|
+
}
|
|
15
|
+
function publicationClientId() {
|
|
16
|
+
if (inMemoryPublicationClientId)
|
|
17
|
+
return inMemoryPublicationClientId;
|
|
18
|
+
try {
|
|
19
|
+
const stored = globalThis.localStorage?.getItem(PUBLICATION_CLIENT_ID_STORAGE_KEY);
|
|
20
|
+
if (stored && PUBLICATION_CLIENT_ID_RE.test(stored)) {
|
|
21
|
+
inMemoryPublicationClientId = stored;
|
|
22
|
+
return stored;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Sandboxed embeds can block storage. Keep a per-page random fallback.
|
|
27
|
+
}
|
|
28
|
+
const generated = newPublicationClientId();
|
|
29
|
+
inMemoryPublicationClientId = generated;
|
|
30
|
+
try {
|
|
31
|
+
globalThis.localStorage?.setItem(PUBLICATION_CLIENT_ID_STORAGE_KEY, generated);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// The in-memory value still scopes retries in this embedded page.
|
|
35
|
+
}
|
|
36
|
+
return generated;
|
|
37
|
+
}
|
|
38
|
+
function resolvePublicationClientId(requesterId) {
|
|
39
|
+
if (requesterId)
|
|
40
|
+
return requesterId;
|
|
41
|
+
if (typeof window === "undefined") {
|
|
42
|
+
throw new Error("Server callers must provide a caller-scoped published chat requester ID");
|
|
43
|
+
}
|
|
44
|
+
return publicationClientId();
|
|
45
|
+
}
|
|
46
|
+
export function parsePublishedChatUrl(value, name) {
|
|
47
|
+
let url;
|
|
48
|
+
try {
|
|
49
|
+
url = new URL(value);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new Error(`${name} must be an absolute URL`);
|
|
53
|
+
}
|
|
54
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
55
|
+
throw new Error(`${name} must use HTTP or HTTPS`);
|
|
56
|
+
}
|
|
57
|
+
if (url.username || url.password) {
|
|
58
|
+
throw new Error(`${name} must not include credentials`);
|
|
59
|
+
}
|
|
60
|
+
return url;
|
|
61
|
+
}
|
|
62
|
+
export function getPublishedChatEndpoints(publicationId, publicationApiUrl = DEFAULT_PUBLICATION_API_URL) {
|
|
63
|
+
if (!PUBLICATION_ID_RE.test(publicationId)) {
|
|
64
|
+
throw new Error("publication-id must be a UUID");
|
|
65
|
+
}
|
|
66
|
+
const apiUrl = parsePublishedChatUrl(publicationApiUrl, "publication-api-url");
|
|
67
|
+
apiUrl.pathname = `${apiUrl.pathname.replace(/\/+$/, "")}/`;
|
|
68
|
+
const publicationPath = `chat-publications/${encodeURIComponent(publicationId)}`;
|
|
69
|
+
const descriptorUrl = new URL(publicationPath, apiUrl);
|
|
70
|
+
return {
|
|
71
|
+
descriptorUrl,
|
|
72
|
+
launchGrantUrl: new URL(`${publicationPath}/launch-grants`, apiUrl),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async function errorMessage(response, fallback) {
|
|
76
|
+
const payload = await response.json().catch(() => null);
|
|
77
|
+
const detail = payload?.detail ?? payload?.message ?? payload?.error;
|
|
78
|
+
return typeof detail === "string" && detail.trim()
|
|
79
|
+
? detail.trim().slice(0, 240)
|
|
80
|
+
: fallback;
|
|
81
|
+
}
|
|
82
|
+
export async function fetchPublishedChatDescriptor(descriptorUrl, fetchImpl = fetch, signal) {
|
|
83
|
+
const url = parsePublishedChatUrl(descriptorUrl, "descriptor-url");
|
|
84
|
+
const response = await fetchImpl(url, {
|
|
85
|
+
method: "GET",
|
|
86
|
+
cache: "no-store",
|
|
87
|
+
credentials: "omit",
|
|
88
|
+
headers: { Accept: "application/json" },
|
|
89
|
+
signal,
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
throw new Error(await errorMessage(response, `Unable to load published chat (${response.status})`));
|
|
93
|
+
}
|
|
94
|
+
const payload = await response.json();
|
|
95
|
+
if (typeof payload?.publication_id !== "string" ||
|
|
96
|
+
typeof payload?.display_name !== "string" ||
|
|
97
|
+
(typeof payload?.description !== "string" && payload?.description !== null) ||
|
|
98
|
+
(typeof payload?.image_url !== "string" && payload?.image_url !== null) ||
|
|
99
|
+
payload?.mode !== "text") {
|
|
100
|
+
throw new Error("Published chat returned an invalid descriptor");
|
|
101
|
+
}
|
|
102
|
+
return payload;
|
|
103
|
+
}
|
|
104
|
+
export async function requestPublishedChatGrant(launchGrantUrl, fetchImpl = fetch, signal, requesterId) {
|
|
105
|
+
const effectiveRequesterId = resolvePublicationClientId(requesterId);
|
|
106
|
+
if (!PUBLICATION_CLIENT_ID_RE.test(effectiveRequesterId)) {
|
|
107
|
+
throw new Error("Published chat browser identifier is invalid");
|
|
108
|
+
}
|
|
109
|
+
const url = parsePublishedChatUrl(launchGrantUrl, "launch-grant-url");
|
|
110
|
+
const response = await fetchImpl(url, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
cache: "no-store",
|
|
113
|
+
credentials: "omit",
|
|
114
|
+
headers: {
|
|
115
|
+
Accept: "application/json",
|
|
116
|
+
"X-Convai-Publication-Client": effectiveRequesterId,
|
|
117
|
+
},
|
|
118
|
+
signal,
|
|
119
|
+
});
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
throw new Error(await errorMessage(response, `Unable to start published chat (${response.status})`));
|
|
122
|
+
}
|
|
123
|
+
const payload = await response.json();
|
|
124
|
+
const launchToken = payload?.launch_token;
|
|
125
|
+
if (typeof launchToken !== "string" ||
|
|
126
|
+
launchToken.length < LAUNCH_TOKEN_MIN_LENGTH ||
|
|
127
|
+
launchToken.length > LAUNCH_TOKEN_MAX_LENGTH) {
|
|
128
|
+
throw new Error("Published chat returned an invalid launch grant");
|
|
129
|
+
}
|
|
130
|
+
return launchToken;
|
|
131
|
+
}
|
|
132
|
+
export async function loadPublishedChatSession(publicationId, publicationApiUrl, fetchImpl = fetch, signal, requesterId) {
|
|
133
|
+
const { descriptorUrl, launchGrantUrl } = getPublishedChatEndpoints(publicationId, publicationApiUrl);
|
|
134
|
+
const descriptor = await fetchPublishedChatDescriptor(descriptorUrl.toString(), fetchImpl, signal);
|
|
135
|
+
if (descriptor.publication_id.toLowerCase() !== publicationId.toLowerCase()) {
|
|
136
|
+
throw new Error("Published chat descriptor does not match publication-id");
|
|
137
|
+
}
|
|
138
|
+
const launchToken = await requestPublishedChatGrant(launchGrantUrl.toString(), fetchImpl, signal, requesterId);
|
|
139
|
+
return { descriptor, launchToken };
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=publicationApi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publicationApi.js","sourceRoot":"","sources":["../../src/embed/publicationApi.ts"],"names":[],"mappings":"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","sourcesContent":["export interface PublishedChatDescriptor {\n publication_id: string;\n display_name: string;\n description: string | null;\n image_url: string | null;\n mode: \"text\";\n}\n\nexport type PublishedChatFetch = typeof fetch;\n\nconst LAUNCH_TOKEN_MIN_LENGTH = 32;\nconst LAUNCH_TOKEN_MAX_LENGTH = 512;\nconst PUBLICATION_ID_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst PUBLICATION_CLIENT_ID_RE = /^[A-Za-z0-9_-]{16,128}$/;\nconst PUBLICATION_CLIENT_ID_STORAGE_KEY = \"convai.published-chat-client-id.v1\";\n\nlet inMemoryPublicationClientId: string | undefined;\n\nexport const DEFAULT_PUBLICATION_API_URL = \"https://api.convai.com\";\n\nexport interface PublishedChatEndpoints {\n descriptorUrl: URL;\n launchGrantUrl: URL;\n}\n\nexport interface PublishedChatSession {\n descriptor: PublishedChatDescriptor;\n launchToken: string;\n}\n\nfunction newPublicationClientId(): string {\n const clientId = globalThis.crypto?.randomUUID?.();\n if (!clientId) {\n throw new Error(\"Published chat requires a secure browser random source\");\n }\n return clientId;\n}\n\nfunction publicationClientId(): string {\n if (inMemoryPublicationClientId) return inMemoryPublicationClientId;\n\n try {\n const stored = globalThis.localStorage?.getItem(PUBLICATION_CLIENT_ID_STORAGE_KEY);\n if (stored && PUBLICATION_CLIENT_ID_RE.test(stored)) {\n inMemoryPublicationClientId = stored;\n return stored;\n }\n } catch {\n // Sandboxed embeds can block storage. Keep a per-page random fallback.\n }\n\n const generated = newPublicationClientId();\n inMemoryPublicationClientId = generated;\n try {\n globalThis.localStorage?.setItem(PUBLICATION_CLIENT_ID_STORAGE_KEY, generated);\n } catch {\n // The in-memory value still scopes retries in this embedded page.\n }\n return generated;\n}\n\nfunction resolvePublicationClientId(requesterId?: string): string {\n if (requesterId) return requesterId;\n if (typeof window === \"undefined\") {\n throw new Error(\n \"Server callers must provide a caller-scoped published chat requester ID\",\n );\n }\n return publicationClientId();\n}\n\nexport function parsePublishedChatUrl(value: string, name: string): URL {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new Error(`${name} must be an absolute URL`);\n }\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new Error(`${name} must use HTTP or HTTPS`);\n }\n if (url.username || url.password) {\n throw new Error(`${name} must not include credentials`);\n }\n return url;\n}\n\nexport function getPublishedChatEndpoints(\n publicationId: string,\n publicationApiUrl = DEFAULT_PUBLICATION_API_URL,\n): PublishedChatEndpoints {\n if (!PUBLICATION_ID_RE.test(publicationId)) {\n throw new Error(\"publication-id must be a UUID\");\n }\n const apiUrl = parsePublishedChatUrl(publicationApiUrl, \"publication-api-url\");\n apiUrl.pathname = `${apiUrl.pathname.replace(/\\/+$/, \"\")}/`;\n const publicationPath = `chat-publications/${encodeURIComponent(publicationId)}`;\n const descriptorUrl = new URL(publicationPath, apiUrl);\n return {\n descriptorUrl,\n launchGrantUrl: new URL(`${publicationPath}/launch-grants`, apiUrl),\n };\n}\n\nasync function errorMessage(response: Response, fallback: string): Promise<string> {\n const payload = await response.json().catch(() => null);\n const detail = payload?.detail ?? payload?.message ?? payload?.error;\n return typeof detail === \"string\" && detail.trim()\n ? detail.trim().slice(0, 240)\n : fallback;\n}\n\nexport async function fetchPublishedChatDescriptor(\n descriptorUrl: string,\n fetchImpl: PublishedChatFetch = fetch,\n signal?: AbortSignal,\n): Promise<PublishedChatDescriptor> {\n const url = parsePublishedChatUrl(descriptorUrl, \"descriptor-url\");\n const response = await fetchImpl(url, {\n method: \"GET\",\n cache: \"no-store\",\n credentials: \"omit\",\n headers: { Accept: \"application/json\" },\n signal,\n });\n if (!response.ok) {\n throw new Error(\n await errorMessage(response, `Unable to load published chat (${response.status})`),\n );\n }\n const payload = await response.json();\n if (\n typeof payload?.publication_id !== \"string\" ||\n typeof payload?.display_name !== \"string\" ||\n (typeof payload?.description !== \"string\" && payload?.description !== null) ||\n (typeof payload?.image_url !== \"string\" && payload?.image_url !== null) ||\n payload?.mode !== \"text\"\n ) {\n throw new Error(\"Published chat returned an invalid descriptor\");\n }\n return payload as PublishedChatDescriptor;\n}\n\nexport async function requestPublishedChatGrant(\n launchGrantUrl: string,\n fetchImpl: PublishedChatFetch = fetch,\n signal?: AbortSignal,\n requesterId?: string,\n): Promise<string> {\n const effectiveRequesterId = resolvePublicationClientId(requesterId);\n if (!PUBLICATION_CLIENT_ID_RE.test(effectiveRequesterId)) {\n throw new Error(\"Published chat browser identifier is invalid\");\n }\n const url = parsePublishedChatUrl(launchGrantUrl, \"launch-grant-url\");\n const response = await fetchImpl(url, {\n method: \"POST\",\n cache: \"no-store\",\n credentials: \"omit\",\n headers: {\n Accept: \"application/json\",\n \"X-Convai-Publication-Client\": effectiveRequesterId,\n },\n signal,\n });\n if (!response.ok) {\n throw new Error(\n await errorMessage(response, `Unable to start published chat (${response.status})`),\n );\n }\n const payload = await response.json();\n const launchToken = payload?.launch_token;\n if (\n typeof launchToken !== \"string\" ||\n launchToken.length < LAUNCH_TOKEN_MIN_LENGTH ||\n launchToken.length > LAUNCH_TOKEN_MAX_LENGTH\n ) {\n throw new Error(\"Published chat returned an invalid launch grant\");\n }\n return launchToken;\n}\n\nexport async function loadPublishedChatSession(\n publicationId: string,\n publicationApiUrl?: string,\n fetchImpl: PublishedChatFetch = fetch,\n signal?: AbortSignal,\n requesterId?: string,\n): Promise<PublishedChatSession> {\n const { descriptorUrl, launchGrantUrl } = getPublishedChatEndpoints(\n publicationId,\n publicationApiUrl,\n );\n const descriptor = await fetchPublishedChatDescriptor(\n descriptorUrl.toString(),\n fetchImpl,\n signal,\n );\n if (descriptor.publication_id.toLowerCase() !== publicationId.toLowerCase()) {\n throw new Error(\"Published chat descriptor does not match publication-id\");\n }\n const launchToken = await requestPublishedChatGrant(\n launchGrantUrl.toString(),\n fetchImpl,\n signal,\n requesterId,\n );\n return { descriptor, launchToken };\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { IConvaiClient } from "../../core/types.js";
|
|
1
|
+
import type { IConvaiClient, PublishedChatConnectOptions } from "../../core/types.js";
|
|
2
|
+
import type { PublishedChatDescriptor } from "../../embed/publicationApi.js";
|
|
2
3
|
interface ConvaiWidgetProps {
|
|
3
4
|
/** Convai client instance (required) */
|
|
4
5
|
convaiClient: IConvaiClient & {
|
|
@@ -22,6 +23,15 @@ interface ConvaiWidgetProps {
|
|
|
22
23
|
* When true, the widget will open directly in voice mode with microphone enabled.
|
|
23
24
|
*/
|
|
24
25
|
defaultVoiceMode?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Mint a fresh single-use grant when a published-chat session is reopened.
|
|
28
|
+
* Required when using this widget with connectWithPublicationGrant().
|
|
29
|
+
*/
|
|
30
|
+
requestPublicationGrant?: () => Promise<string>;
|
|
31
|
+
/** Core endpoint override used with requestPublicationGrant. */
|
|
32
|
+
publishedChatConnectOptions?: PublishedChatConnectOptions;
|
|
33
|
+
/** Public descriptor used to label a credential-free published session. */
|
|
34
|
+
publishedChatDescriptor?: Pick<PublishedChatDescriptor, "display_name" | "image_url">;
|
|
25
35
|
}
|
|
26
36
|
/**
|
|
27
37
|
* ConvaiWidget - A consolidated chat widget component for real-time conversations
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConvaiWidget.d.ts","sourceRoot":"","sources":["../../../src/react/components/ConvaiWidget.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ConvaiWidget.d.ts","sourceRoot":"","sources":["../../../src/react/components/ConvaiWidget.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,aAAa,EACb,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAI1B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAwC1E,UAAU,iBAAiB;IACzB,wCAAwC;IACxC,YAAY,EAAE,aAAa,GAAG;QAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,mBAAmB,EAAE,OAAO,CAAC;KAC9B,CAAC;IACF;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,gEAAgE;IAChE,2BAA2B,CAAC,EAAE,2BAA2B,CAAC;IAC1D,2EAA2E;IAC3E,uBAAuB,CAAC,EAAE,IAAI,CAC5B,uBAAuB,EACvB,cAAc,GAAG,WAAW,CAC7B,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA46BpD,CAAC"}
|
|
@@ -7,6 +7,7 @@ import { PiWaveform } from "react-icons/pi";
|
|
|
7
7
|
import { RoomContext } from "@livekit/components-react";
|
|
8
8
|
import { useCharacterInfo } from "../hooks/useCharacterInfo.js";
|
|
9
9
|
import { AudioRenderer } from "../../vanilla/AudioRenderer.js";
|
|
10
|
+
import { freshPublicationGrantOptions } from "../../core/publishedChat.js";
|
|
10
11
|
// Import styled components and theme from rtc-widget
|
|
11
12
|
import { ConvaiContainer, MorphingContainer, ButtonContent, Logo, ChatContent, Header, Title, CloseButton, SettingsButton, Content, Footer, InputContainer, Input, ActionButton, aeroTheme, iosTransitions, ConvaiLogo, ChevronDown, } from "./rtc-widget/index.js";
|
|
12
13
|
// Import message components
|
|
@@ -51,20 +52,36 @@ import { MessageList, SettingsTray, VoiceModeOverlay, FloatingVideo, } from "./r
|
|
|
51
52
|
* }
|
|
52
53
|
* ```
|
|
53
54
|
*/
|
|
54
|
-
export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare = true, defaultVoiceMode = true, }) => {
|
|
55
|
+
export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare = true, defaultVoiceMode = true, requestPublicationGrant, publishedChatConnectOptions, publishedChatDescriptor, }) => {
|
|
56
|
+
const isTextOnly = Boolean(requestPublicationGrant) || convaiClient.connectionType === "text";
|
|
55
57
|
// Fetch character information dynamically from client's stored values
|
|
56
|
-
const { name:
|
|
58
|
+
const { name: fetchedCharacterName, image: fetchedCharacterImage, isLoading: isFetchedCharacterLoading, } = useCharacterInfo(convaiClient.characterId || undefined, convaiClient.apiKey || undefined, convaiClient.authToken || undefined);
|
|
59
|
+
const characterName = publishedChatDescriptor?.display_name || fetchedCharacterName;
|
|
60
|
+
const characterImage = publishedChatDescriptor?.image_url ?? fetchedCharacterImage;
|
|
61
|
+
const isCharacterLoading = publishedChatDescriptor
|
|
62
|
+
? false
|
|
63
|
+
: isFetchedCharacterLoading;
|
|
57
64
|
// UI State
|
|
58
65
|
const [isOpen, setIsOpen] = useState(false);
|
|
59
66
|
const [inputValue, setInputValue] = useState("");
|
|
60
67
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
|
61
|
-
const [isVoiceMode, setIsVoiceMode] = useState(defaultVoiceMode);
|
|
68
|
+
const [isVoiceMode, setIsVoiceMode] = useState(defaultVoiceMode && !isTextOnly);
|
|
62
69
|
const [isMuted, setIsMuted] = useState(false); // Character audio starts unmuted
|
|
63
70
|
const [isVideoVisible, setIsVideoVisible] = useState(false);
|
|
64
71
|
const [hasEnteredDefaultVoiceMode, setHasEnteredDefaultVoiceMode] = useState(false);
|
|
65
72
|
const settingsButtonRef = useRef(null);
|
|
66
73
|
/** True after we have connected at least once; used to call reconnect() instead of connect() when opening after disconnect */
|
|
67
74
|
const hasConnectedBeforeRef = useRef(false);
|
|
75
|
+
/** Prevent repeated open clicks from minting multiple one-time publication grants. */
|
|
76
|
+
const isStartingConnectionRef = useRef(false);
|
|
77
|
+
/** Avoid opening a room after this widget has been removed from the tree. */
|
|
78
|
+
const isUnmountedRef = useRef(false);
|
|
79
|
+
// Only published sessions wait for Core's bot-ready handshake. A normal
|
|
80
|
+
// authenticated text session still uses the text-only layout, but can send
|
|
81
|
+
// as soon as it connects.
|
|
82
|
+
const requiresBotReadyForText = Boolean(requestPublicationGrant) || convaiClient.isPublishedChatSession;
|
|
83
|
+
const canSendText = convaiClient.state.isConnected &&
|
|
84
|
+
(!requiresBotReadyForText || convaiClient.isBotReady);
|
|
68
85
|
// Character info from API (using client's stored values)
|
|
69
86
|
const selectedCharacter = {
|
|
70
87
|
id: convaiClient.characterId || "unknown",
|
|
@@ -108,21 +125,44 @@ export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare =
|
|
|
108
125
|
};
|
|
109
126
|
// Event Handlers
|
|
110
127
|
const handleToggle = async () => {
|
|
128
|
+
if (isStartingConnectionRef.current) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
111
131
|
// Connect on first click if not already connected/connecting
|
|
112
132
|
if (!convaiClient.state.isConnected && !convaiClient.state.isConnecting) {
|
|
133
|
+
isStartingConnectionRef.current = true;
|
|
113
134
|
try {
|
|
114
|
-
|
|
115
|
-
|
|
135
|
+
if (requestPublicationGrant || convaiClient.isPublishedChatSession) {
|
|
136
|
+
if (!requestPublicationGrant) {
|
|
137
|
+
throw new Error("Published chat widgets require requestPublicationGrant to start a new session");
|
|
138
|
+
}
|
|
139
|
+
if (!convaiClient.connectWithPublicationGrant) {
|
|
140
|
+
throw new Error("This Convai client does not support published chat connections");
|
|
141
|
+
}
|
|
142
|
+
const launchToken = await requestPublicationGrant();
|
|
143
|
+
if (isUnmountedRef.current)
|
|
144
|
+
return;
|
|
145
|
+
await convaiClient.connectWithPublicationGrant(launchToken, freshPublicationGrantOptions(publishedChatConnectOptions));
|
|
146
|
+
}
|
|
147
|
+
else if (hasConnectedBeforeRef.current &&
|
|
148
|
+
typeof convaiClient.reconnect === "function") {
|
|
116
149
|
await convaiClient.reconnect();
|
|
117
150
|
}
|
|
118
151
|
else {
|
|
119
152
|
await convaiClient.connect();
|
|
120
153
|
}
|
|
154
|
+
if (isUnmountedRef.current) {
|
|
155
|
+
await convaiClient.disconnect();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
121
158
|
setIsOpen(true); // Open widget after successful connection
|
|
122
159
|
}
|
|
123
160
|
catch (error) {
|
|
124
161
|
console.error("Failed to connect:", error);
|
|
125
162
|
}
|
|
163
|
+
finally {
|
|
164
|
+
isStartingConnectionRef.current = false;
|
|
165
|
+
}
|
|
126
166
|
}
|
|
127
167
|
else {
|
|
128
168
|
// Just toggle open/close if already connected
|
|
@@ -136,7 +176,7 @@ export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare =
|
|
|
136
176
|
setInputValue(e.target.value);
|
|
137
177
|
};
|
|
138
178
|
const handleSend = () => {
|
|
139
|
-
if (inputValue.trim() &&
|
|
179
|
+
if (inputValue.trim() && canSendText) {
|
|
140
180
|
convaiClient.sendUserTextMessage(inputValue);
|
|
141
181
|
setInputValue("");
|
|
142
182
|
}
|
|
@@ -209,6 +249,12 @@ export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare =
|
|
|
209
249
|
const handleSettingsToggle = () => {
|
|
210
250
|
setIsSettingsOpen(!isSettingsOpen);
|
|
211
251
|
};
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
isUnmountedRef.current = false;
|
|
254
|
+
return () => {
|
|
255
|
+
isUnmountedRef.current = true;
|
|
256
|
+
};
|
|
257
|
+
}, []);
|
|
212
258
|
// Remember that we have connected at least once (so we use reconnect() next time after disconnect)
|
|
213
259
|
useEffect(() => {
|
|
214
260
|
if (convaiClient.state.isConnected) {
|
|
@@ -232,6 +278,11 @@ export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare =
|
|
|
232
278
|
setIsVideoVisible(convaiClient.isVideoEnabled);
|
|
233
279
|
}, [convaiClient.isVideoEnabled]);
|
|
234
280
|
// Ensure microphone is muted when not in voice mode
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
if (isTextOnly && isVoiceMode) {
|
|
283
|
+
setIsVoiceMode(false);
|
|
284
|
+
}
|
|
285
|
+
}, [isTextOnly, isVoiceMode]);
|
|
235
286
|
useEffect(() => {
|
|
236
287
|
const ensureMicState = async () => {
|
|
237
288
|
if (!isVoiceMode && !convaiClient.isAudioMuted) {
|
|
@@ -245,6 +296,7 @@ export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare =
|
|
|
245
296
|
useEffect(() => {
|
|
246
297
|
if (isOpen &&
|
|
247
298
|
defaultVoiceMode &&
|
|
299
|
+
!isTextOnly &&
|
|
248
300
|
convaiClient.state.isConnected &&
|
|
249
301
|
!hasEnteredDefaultVoiceMode &&
|
|
250
302
|
!isVoiceMode) {
|
|
@@ -260,7 +312,7 @@ export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare =
|
|
|
260
312
|
};
|
|
261
313
|
enterVoiceMode();
|
|
262
314
|
}
|
|
263
|
-
}, [isOpen, defaultVoiceMode, convaiClient.state.isConnected, hasEnteredDefaultVoiceMode, isVoiceMode, convaiClient]);
|
|
315
|
+
}, [isOpen, defaultVoiceMode, isTextOnly, convaiClient.state.isConnected, hasEnteredDefaultVoiceMode, isVoiceMode, convaiClient]);
|
|
264
316
|
// Close settings when clicking outside
|
|
265
317
|
useEffect(() => {
|
|
266
318
|
const handleClickOutside = (event) => {
|
|
@@ -495,7 +547,7 @@ export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare =
|
|
|
495
547
|
flexShrink: 0,
|
|
496
548
|
padding: 0,
|
|
497
549
|
outline: "none",
|
|
498
|
-
}, "aria-label": "Exit Voice Mode", title: "Exit voice mode", children: _jsx(FiPhoneOff, { size: 20 }) }, "voice-mode-exit")) : (_jsx(_Fragment, { children: _jsxs(InputContainer, { style: { flex: 1, position: "relative" }, children: [_jsx(Input, { type: "text", placeholder: "Conversation", value: inputValue, onChange: handleInputChange, onKeyPress: handleKeyPress, style: { paddingRight: "2.75rem" } }), _jsx("div", { style: {
|
|
550
|
+
}, "aria-label": "Exit Voice Mode", title: "Exit voice mode", children: _jsx(FiPhoneOff, { size: 20 }) }, "voice-mode-exit")) : (_jsx(_Fragment, { children: _jsxs(InputContainer, { style: { flex: 1, position: "relative" }, children: [_jsx(Input, { type: "text", placeholder: "Conversation", value: inputValue, onChange: handleInputChange, onKeyPress: handleKeyPress, disabled: !canSendText, style: { paddingRight: "2.75rem" } }), _jsx("div", { style: {
|
|
499
551
|
position: "absolute",
|
|
500
552
|
right: "0.375rem",
|
|
501
553
|
top: "50%",
|
|
@@ -510,10 +562,10 @@ export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare =
|
|
|
510
562
|
alignItems: "center",
|
|
511
563
|
justifyContent: "center",
|
|
512
564
|
zIndex: 2,
|
|
513
|
-
}, children: _jsx(AnimatePresence, { mode: "wait", children: inputValue.length > 0 ? (_jsx(ActionButton, { initial: { y: "100%" }, animate: { y: "0%" }, exit: { y: "-100%" }, transition: {
|
|
565
|
+
}, children: _jsx(AnimatePresence, { mode: "wait", children: inputValue.length > 0 || isTextOnly ? (_jsx(ActionButton, { initial: { y: "100%" }, animate: { y: "0%" }, exit: { y: "-100%" }, transition: {
|
|
514
566
|
duration: 0.1,
|
|
515
567
|
ease: "linear",
|
|
516
|
-
}, onClick: handleSend, whileTap: { scale: 0.9 }, style: {
|
|
568
|
+
}, onClick: handleSend, disabled: inputValue.length === 0 || !canSendText, whileTap: { scale: 0.9 }, style: {
|
|
517
569
|
position: "absolute",
|
|
518
570
|
top: 0,
|
|
519
571
|
left: 0,
|
|
@@ -548,7 +600,11 @@ export const ConvaiWidget = ({ convaiClient, showVideo = true, showScreenShare =
|
|
|
548
600
|
flexShrink: 0,
|
|
549
601
|
padding: 0,
|
|
550
602
|
outline: "none",
|
|
551
|
-
}, "aria-label": "Enter Voice Mode", title: "Use voice mode", children: _jsx(PiWaveform, { size: 20 }) }, "voice")) }) })] }) })) })] }))] }), convaiClient.state.isConnected && (_jsx(SettingsTray, { isOpen: isSettingsOpen, onReset:
|
|
603
|
+
}, "aria-label": "Enter Voice Mode", title: "Use voice mode", children: _jsx(PiWaveform, { size: 20 }) }, "voice")) }) })] }) })) })] }))] }), convaiClient.state.isConnected && (_jsx(SettingsTray, { isOpen: isSettingsOpen, onReset: convaiClient.isPublishedChatSession && !requestPublicationGrant
|
|
604
|
+
? undefined
|
|
605
|
+
: handleReset, onDisconnect: convaiClient.isPublishedChatSession && !requestPublicationGrant
|
|
606
|
+
? undefined
|
|
607
|
+
: handleDisconnect, onToggleVideo: convaiClient.connectionType === "video" && showVideo
|
|
552
608
|
? handleToggleVideo
|
|
553
609
|
: undefined, isVideoVisible: isVideoVisible, onToggleScreenShare: convaiClient.connectionType === "video" && showScreenShare
|
|
554
610
|
? handleToggleScreenShare
|