@burdenoff/website-sdk 2026.1104.3 → 2026.1105.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 +51 -6
- package/dist/avatar-live-view/index.d.mts +1 -1
- package/dist/avatar-live-view/index.d.ts +1 -1
- package/dist/avatar-live-view/index.js +482 -64
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +476 -58
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/avatar-mic/index.d.mts +193 -0
- package/dist/avatar-mic/index.d.ts +193 -0
- package/dist/avatar-mic/index.js +738 -0
- package/dist/avatar-mic/index.js.map +1 -0
- package/dist/avatar-mic/index.mjs +736 -0
- package/dist/avatar-mic/index.mjs.map +1 -0
- package/dist/{chunk-5JQGE3XI.mjs → chunk-7HE33NBS.mjs} +4 -111
- package/dist/chunk-7HE33NBS.mjs.map +1 -0
- package/dist/chunk-JZS6FQT6.mjs +127 -0
- package/dist/chunk-JZS6FQT6.mjs.map +1 -0
- package/dist/{chunk-22OT6E26.js → chunk-MBVXDFVC.js} +18 -127
- package/dist/chunk-MBVXDFVC.js.map +1 -0
- package/dist/{chunk-7MSB3EUI.mjs → chunk-MVNN6LLO.mjs} +4 -3
- package/dist/chunk-MVNN6LLO.mjs.map +1 -0
- package/dist/chunk-N7E6Z2GF.js +132 -0
- package/dist/chunk-N7E6Z2GF.js.map +1 -0
- package/dist/{chunk-WSEQ2I6I.js → chunk-XIZE37IB.js} +4 -3
- package/dist/chunk-XIZE37IB.js.map +1 -0
- package/dist/components/explore.d.mts +1 -1
- package/dist/components/explore.d.ts +1 -1
- package/dist/components/explore.js +20 -20
- package/dist/components/explore.mjs +2 -2
- package/dist/{explore-BV4dRwoC.d.ts → explore-Dfru4j4m.d.ts} +45 -31
- package/dist/{explore-avatar-live-config-DRpS-mqQ.d.ts → explore-avatar-live-config-C-OwXNje.d.ts} +8 -0
- package/dist/{explore-avatar-live-config-B6_p0q1c.d.mts → explore-avatar-live-config-OO0hvQCK.d.mts} +8 -0
- package/dist/{explore-BgEeGPvY.d.mts → explore-ei8uoko9.d.mts} +45 -31
- package/dist/index.d.mts +11 -6
- package/dist/index.d.ts +11 -6
- package/dist/index.js +70 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-22OT6E26.js.map +0 -1
- package/dist/chunk-5JQGE3XI.mjs.map +0 -1
- package/dist/chunk-7MSB3EUI.mjs.map +0 -1
- package/dist/chunk-O4GS3XPZ.js +0 -10
- package/dist/chunk-O4GS3XPZ.js.map +0 -1
- package/dist/chunk-TT3MHN42.mjs +0 -8
- package/dist/chunk-TT3MHN42.mjs.map +0 -1
- package/dist/chunk-WSEQ2I6I.js.map +0 -1
|
@@ -31,7 +31,8 @@ function liveConfigComplete(live) {
|
|
|
31
31
|
const key = live.turnstileSiteKey;
|
|
32
32
|
const allowTest = live.allowTestSiteKey;
|
|
33
33
|
const endUnattended = live.endUnattended;
|
|
34
|
-
|
|
34
|
+
const mic = live.mic;
|
|
35
|
+
return typeof live.load === "function" && typeof live.site === "string" && isAvatarSite(live.site) && typeof live.manifestVersion === "string" && isAvatarVersion(live.manifestVersion) && (key === void 0 || key === "" || typeof key === "string" && SITE_KEY.test(key)) && (allowTest === void 0 || typeof allowTest === "boolean") && (endUnattended === void 0 || typeof endUnattended === "boolean") && (mic === void 0 || typeof mic === "boolean");
|
|
35
36
|
}
|
|
36
37
|
function turnstileSiteKeyOf(live) {
|
|
37
38
|
const key = live.turnstileSiteKey;
|
|
@@ -47,5 +48,5 @@ function isAvatarLiveLocaleReviewed(locale) {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export { avatarApiBase, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion, isRealTurnstileSiteKey, liveConfigComplete, turnstileSiteKeyOf };
|
|
50
|
-
//# sourceMappingURL=chunk-
|
|
51
|
-
//# sourceMappingURL=chunk-
|
|
51
|
+
//# sourceMappingURL=chunk-MVNN6LLO.mjs.map
|
|
52
|
+
//# sourceMappingURL=chunk-MVNN6LLO.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/explore-avatar-live-config.ts"],"names":[],"mappings":";AAwFO,IAAM,mBAAA,GAAsB,oBAAA;AAEnC,IAAM,IAAA,GAAO,wBAAA;AACb,IAAM,OAAA,GAAU,wBAAA;AAChB,IAAM,QAAA,GAAW,wBAAA;AAKjB,IAAM,aAAA,GAAgB,wBAAA;AAMtB,IAAM,aAAA,GAAgB,0BAAA;AACtB,IAAM,iCAAiB,IAAI,GAAA,CAAI,CAAC,WAAA,EAAa,WAAA,EAAa,OAAO,CAAC,CAAA;AAG3D,SAAS,aAAa,KAAA,EAAwB;AACnD,EAAA,OAAO,IAAA,CAAK,KAAK,KAAK,CAAA;AACxB;AAGO,SAAS,gBAAgB,KAAA,EAAwB;AACtD,EAAA,OAAO,OAAA,CAAQ,KAAK,KAAK,CAAA;AAC3B;AAOO,SAAS,cAAc,UAAA,EAA+C;AAC3E,EAAA,IAAI,CAAC,YAAY,OAAO,IAAA;AACxB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAI,IAAI,UAAU,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GACJ,IAAI,QAAA,KAAa,OAAA,IAAW,eAAe,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,WAAA,EAAa,CAAA;AAC3E,EAAA,IAAI,GAAA,CAAI,QAAA,KAAa,QAAA,IAAY,CAAC,OAAO,OAAO,IAAA;AAChD,EAAA,OAAO,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,EAAG,mBAAmB,CAAA,CAAA;AAC5C;AAGO,SAAS,uBAAuB,GAAA,EAAsB;AAC3D,EAAA,OAAO,cAAc,IAAA,CAAK,GAAG,KAAK,CAAC,aAAA,CAAc,KAAK,GAAG,CAAA;AAC3D;AAMO,SAAS,mBAAmB,IAAA,EAAuC;AACxE,EAAA,MAAM,MAAe,IAAA,CAAK,gBAAA;AAC1B,EAAA,MAAM,YAAqB,IAAA,CAAK,gBAAA;AAChC,EAAA,MAAM,gBAAyB,IAAA,CAAK,aAAA;AACpC,EAAA,MAAM,MAAe,IAAA,CAAK,GAAA;AAC1B,EAAA,OACE,OAAO,KAAK,IAAA,KAAS,UAAA,IACrB,OAAO,IAAA,CAAK,IAAA,KAAS,YACrB,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA,IACtB,OAAO,KAAK,eAAA,KAAoB,QAAA,IAChC,gBAAgB,IAAA,CAAK,eAAe,MACnC,GAAA,KAAQ,MAAA,IACP,QAAQ,EAAA,IACP,OAAO,QAAQ,QAAA,IAAY,QAAA,CAAS,KAAK,GAAG,CAAA,CAAA,KAC9C,cAAc,MAAA,IAAa,OAAO,cAAc,SAAA,CAAA,KAChD,aAAA,KAAkB,UAAa,OAAO,aAAA,KAAkB,eACxD,GAAA,KAAQ,MAAA,IAAa,OAAO,GAAA,KAAQ,SAAA,CAAA;AAEzC;AAMO,SAAS,mBACd,IAAA,EACe;AACf,EAAA,MAAM,MAAM,IAAA,CAAK,gBAAA;AACjB,EAAA,IAAI,OAAO,QAAQ,QAAA,IAAY,CAAC,SAAS,IAAA,CAAK,GAAG,GAAG,OAAO,IAAA;AAC3D,EAAA,IAAI,KAAK,gBAAA,KAAqB,KAAA,IAAS,CAAC,sBAAA,CAAuB,GAAG,CAAA,EAAG;AACnE,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA;AACT;AAMA,IAAM,QAAA,mBAAuC,IAAI,GAAA,CAAmB,CAAC,IAAI,CAAC,CAAA;AAGnE,SAAS,2BAA2B,MAAA,EAAgC;AACzE,EAAA,OAAO,QAAA,CAAS,IAAI,MAAM,CAAA;AAC5B","file":"chunk-MVNN6LLO.mjs","sourcesContent":["/**\n * BOFF-9797. The small part of the live avatar that the panel itself needs, whether or not it is ever opened: what a\n * host's `avatar.live` is, whether the live avatar can run on this page at all, and where its requests go.\n *\n * Everything else (the view, its state machine, the requests, the words, the bot check) is a separate chunk that a\n * site only downloads when its visitor first opens the Avatar view (see `explore-avatar-live.tsx`). That is why this\n * file is separate and kept to a few lines: it is the only part of the live avatar in the package root for a site that\n * does not pass `avatar.live`, and the part of it that decides, without loading anything, whether the chunk is wanted.\n * Types only, plus plain functions: nothing here imports a component, a context or a string table.\n */\nimport type { ExploreLocale } from \"./explore-i18n\";\nimport type { AvatarLiveModule } from \"../avatar-live/types\";\nimport type { UseExploreChatResult } from \"../hooks/use-explore-chat\";\n\n/**\n * BOFF-9797. A host's opt-in to the live avatar on its panel's Avatar tab. `site`, `load` and `manifestVersion` are\n * required; a config that is incomplete, or has an optional field of the wrong shape, leaves the tab as it was (and a\n * warning in the console).\n */\nexport interface ExplorePanelAvatarLive {\n /** The site, as the live avatar service knows it: `healthybowl`. */\n site: string;\n /**\n * Loads the live avatar's room module: `() => import('@burdenoff/website-sdk/avatar-live')`. Called only after the\n * visitor presses Start on the consent card, never on page load or on opening the tab. That entry needs the\n * package's optional peer dependency for media rooms (see the README).\n */\n load: () => Promise<AvatarLiveModule>;\n /**\n * The site's Cloudflare Turnstile site key, for the bot check that runs when the visitor presses Start. Used only when\n * the server says it checks for bots (its availability's `botCheck`, contract v1.2), or says nothing of it (a server\n * older than v1.2, which always checks): such a server's live avatar then needs a key, and without one the tab stays\n * as it was. A server that runs no bot check needs none, and no Cloudflare script is loaded for it, key or not.\n */\n turnstileSiteKey?: string;\n /**\n * Whether `turnstileSiteKey` may be one of Cloudflare's test keys (`1x00000000000000000000AA` and keys of their\n * shape). `websiteAvatarLive` sets it: true on alpha only, false everywhere else, so a production page never runs a\n * test widget. Unset (a config made by hand): any key of a valid shape, as before.\n */\n allowTestSiteKey?: boolean;\n /** The version of the suggested-question list the panel shows (its `examplePrompts`), such as `hb-en-1`. */\n manifestVersion: string;\n /**\n * End a session nobody is attending to (BOFF-9797 stage A). A session keeps playing behind the Chat tab and on a\n * hidden page, so the answer under way is heard to its end; with this set, out of sight it also ends:\n *\n * - **once its answer has played out**, at once: the idle tail after an answer runs only while the Avatar view is in\n * sight (the shared lifecycle rule; see `AvatarLiveView`'s `attend`);\n * - **when the browser holds its sound back** (autoplay refused, or the audio context suspended), after a short grace:\n * the \"Tap to turn on sound\" button that would let it play is out of sight, so nobody hears it.\n *\n * Both end it with reason `user`. Unset or false: such a session plays on to the end the server planned for it, as\n * before.\n */\n endUnattended?: boolean;\n /**\n * Offer a mic in the Avatar tab's own question box (BOFF-9797 Stage C): tap to start, tap to stop, up to thirty\n * seconds, the text into the box for the visitor to correct and Send (nothing is sent by itself). Offered only where\n * the server's availability answer also carries a mic offer (`stt`, while the gateway's website speech-to-text is on)\n * and the box takes a question, after a mic notice of its own on the first tap. The mic's code is a chunk of its own,\n * loaded the first time it is used. Unset or false: no mic, as before.\n */\n mic?: boolean;\n}\n\n/**\n * What the live avatar reads of the panel's conversation: the transcript, whether it is read-only and the token, and\n * `reset` (the chat's own New chat) for its New chat button where the conversation can never be read. Where the server\n * reads typed questions too (contract v1.3), the Avatar tab's own question box asks through the same conversation:\n * `send` (with `surface: \"AVATAR\"`), and `canSend`, `phase`, `activity` and `catalog` to say when it can and what the\n * answer is doing, and `resent` to follow a question asked there that Chat's Retry sent again.\n */\nexport type AvatarConversation = Pick<\n UseExploreChatResult,\n | \"messages\"\n | \"isSharedView\"\n | \"getConversationToken\"\n | \"reset\"\n | \"send\"\n | \"canSend\"\n | \"phase\"\n | \"activity\"\n | \"catalog\"\n | \"resent\"\n>;\n\n/** The web branch's routes, under the gateway's origin. */\nexport const AVATAR_WEB_API_PATH = \"/api/web-avatar/v1\";\n\nconst SITE = /^[a-z][a-z0-9-]{1,31}$/;\nconst VERSION = /^[A-Za-z0-9._-]{1,32}$/;\nconst SITE_KEY = /^[A-Za-z0-9_-]{1,128}$/;\n/**\n * The shape of Cloudflare's dummy site keys: a digit, `x`, a run of zeros and two letters (`1x00000000000000000000AA`,\n * `2x00000000000000000000AB`, `3x00000000000000000000FF`, …). Also catches an all-zero `0x…` placeholder.\n */\nconst TEST_SITE_KEY = /^\\dx0{16,}[A-Za-z]{2}$/;\n/**\n * A real-looking site key. Cloudflare documents no format for real keys; every key it issues starts `0x` (in practice\n * `0x4AAAAAAA…`, about 24 characters), in letters, digits, `_` and `-`. The length is kept loose (18 to 64 in all) so a\n * longer key Cloudflare might issue later still passes, and the charset matches what the panel accepts.\n */\nconst REAL_SITE_KEY = /^0x[A-Za-z0-9_-]{16,62}$/;\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** A site slug the contract accepts (`healthybowl`). */\nexport function isAvatarSite(value: string): boolean {\n return SITE.test(value);\n}\n\n/** A consent or manifest version the contract accepts (`hb-en-1`). */\nexport function isAvatarVersion(value: string): boolean {\n return VERSION.test(value);\n}\n\n/**\n * The web branch's base URL on the SDK's configured gateway: the gateway URL's origin, plus\n * {@link AVATAR_WEB_API_PATH}. Null for anything that is not an `https:` URL (or `http:` on this machine, for local\n * development).\n */\nexport function avatarApiBase(gatewayUrl: string | undefined): string | null {\n if (!gatewayUrl) return null;\n let url: URL;\n try {\n url = new URL(gatewayUrl);\n } catch {\n return null;\n }\n const local =\n url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase());\n if (url.protocol !== \"https:\" && !local) return null;\n return `${url.origin}${AVATAR_WEB_API_PATH}`;\n}\n\n/** A real-looking Turnstile site key: `0x…`, and not of the shape of Cloudflare's test keys. */\nexport function isRealTurnstileSiteKey(key: string): boolean {\n return REAL_SITE_KEY.test(key) && !TEST_SITE_KEY.test(key);\n}\n\n/**\n * Whether a host's `avatar.live` has everything the live avatar needs, in the shapes the contract accepts. The site\n * key is optional (an empty one is none), and checked only when it is there.\n */\nexport function liveConfigComplete(live: ExplorePanelAvatarLive): boolean {\n const key: unknown = live.turnstileSiteKey;\n const allowTest: unknown = live.allowTestSiteKey;\n const endUnattended: unknown = live.endUnattended;\n const mic: unknown = live.mic;\n return (\n typeof live.load === \"function\" &&\n typeof live.site === \"string\" &&\n isAvatarSite(live.site) &&\n typeof live.manifestVersion === \"string\" &&\n isAvatarVersion(live.manifestVersion) &&\n (key === undefined ||\n key === \"\" ||\n (typeof key === \"string\" && SITE_KEY.test(key))) &&\n (allowTest === undefined || typeof allowTest === \"boolean\") &&\n (endUnattended === undefined || typeof endUnattended === \"boolean\") &&\n (mic === undefined || typeof mic === \"boolean\")\n );\n}\n\n/**\n * The site key a bot check at Start may use, or null when there is none: no key, one of no valid shape, or one of\n * Cloudflare's test keys where `allowTestSiteKey` is false.\n */\nexport function turnstileSiteKeyOf(\n live: ExplorePanelAvatarLive,\n): string | null {\n const key = live.turnstileSiteKey;\n if (typeof key !== \"string\" || !SITE_KEY.test(key)) return null;\n if (live.allowTestSiteKey === false && !isRealTurnstileSiteKey(key)) {\n return null;\n }\n return key;\n}\n\n/**\n * The locales whose live-avatar strings have been reviewed. The live avatar runs only in these; see\n * `explore-avatar-i18n.ts`. English only, until a named reviewer signs off on another.\n */\nconst REVIEWED: ReadonlySet<ExploreLocale> = new Set<ExploreLocale>([\"en\"]);\n\n/** Whether the live avatar may run in `locale`: its strings have been reviewed. */\nexport function isAvatarLiveLocaleReviewed(locale: ExploreLocale): boolean {\n return REVIEWED.has(locale);\n}\n"]}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/components/explore-fill.ts
|
|
4
|
+
function fill(template, values) {
|
|
5
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/components/explore-answer-text.ts
|
|
9
|
+
var CODE_FENCE = /^\s*(?:```|~~~)/;
|
|
10
|
+
var TABLE_ROW_LINE = /^\s{0,3}\|/;
|
|
11
|
+
var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
|
|
12
|
+
var LINKED_IMAGE = /\[!\[([^[\]]*)\]\([^)\s]*(?:\s+"[^"]*")?\)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
13
|
+
var IMAGE = /!\[[^[\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g;
|
|
14
|
+
var LINK = /\[([^[\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
15
|
+
var LIST_MARKER = /^(\s*)(?:[-*+]|\d{1,9}[.)])[ \t]+/;
|
|
16
|
+
function spoken(mode) {
|
|
17
|
+
return mode !== "clipboard";
|
|
18
|
+
}
|
|
19
|
+
function readableLine(raw, mode, item = true) {
|
|
20
|
+
let line = raw;
|
|
21
|
+
if (line.includes("](")) {
|
|
22
|
+
line = line.replace(LINKED_IMAGE, (_match, alt, href) => {
|
|
23
|
+
if (spoken(mode)) return "";
|
|
24
|
+
return alt.trim() === "" ? href : `${alt.trim()} (${href})`;
|
|
25
|
+
});
|
|
26
|
+
line = line.replace(IMAGE, "");
|
|
27
|
+
line = spoken(mode) ? (
|
|
28
|
+
// A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
|
|
29
|
+
// prompt asks for two to five links in every answer. The label IS the words.
|
|
30
|
+
line.replace(LINK, "$1")
|
|
31
|
+
) : line.replace(LINK, "$1 ($2)");
|
|
32
|
+
}
|
|
33
|
+
line = spoken(mode) ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
34
|
+
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
35
|
+
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
36
|
+
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
|
|
37
|
+
if (mode === "display" && item) line = line.replace(LIST_MARKER, "$1");
|
|
38
|
+
line = line.replace(/`([^`]+)`/g, "$1");
|
|
39
|
+
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
40
|
+
line = line.replace(/__([^_]+)__/g, "$1");
|
|
41
|
+
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
42
|
+
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
43
|
+
return line.replace(/[ \t]+$/, "");
|
|
44
|
+
}
|
|
45
|
+
function tableCells(row) {
|
|
46
|
+
const trimmed = row.trim();
|
|
47
|
+
const cells = [];
|
|
48
|
+
let cell = "";
|
|
49
|
+
for (let at = 0; at < trimmed.length; at += 1) {
|
|
50
|
+
const char = trimmed[at] ?? "";
|
|
51
|
+
if (char === "\\") {
|
|
52
|
+
cell += char + (trimmed[at + 1] ?? "");
|
|
53
|
+
at += 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (char === "|") {
|
|
57
|
+
cells.push(cell.trim());
|
|
58
|
+
cell = "";
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
cell += char;
|
|
62
|
+
}
|
|
63
|
+
cells.push(cell.trim());
|
|
64
|
+
if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
|
|
65
|
+
if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
|
|
66
|
+
return cells;
|
|
67
|
+
}
|
|
68
|
+
function asSentence(text) {
|
|
69
|
+
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
70
|
+
}
|
|
71
|
+
function spokenTable(rows) {
|
|
72
|
+
if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
|
|
73
|
+
const labels = tableCells(rows[0] ?? "");
|
|
74
|
+
const spoken2 = [];
|
|
75
|
+
for (const row of rows.slice(2)) {
|
|
76
|
+
const parts = [];
|
|
77
|
+
tableCells(row).forEach((value, column) => {
|
|
78
|
+
if (value === "") return;
|
|
79
|
+
const label = column === 0 ? "" : labels[column] ?? "";
|
|
80
|
+
parts.push(label === "" ? value : `${label}: ${value}`);
|
|
81
|
+
});
|
|
82
|
+
if (parts.length > 0) spoken2.push(asSentence(parts.join(". ")));
|
|
83
|
+
}
|
|
84
|
+
return spoken2;
|
|
85
|
+
}
|
|
86
|
+
function readableText(markdown, mode) {
|
|
87
|
+
const lines = [];
|
|
88
|
+
let inFence = false;
|
|
89
|
+
let table = [];
|
|
90
|
+
const flushTable = () => {
|
|
91
|
+
if (table.length === 0) return;
|
|
92
|
+
for (const line of spokenTable(table)) {
|
|
93
|
+
lines.push(readableLine(line, mode, false));
|
|
94
|
+
}
|
|
95
|
+
table = [];
|
|
96
|
+
};
|
|
97
|
+
for (const raw of markdown.split("\n")) {
|
|
98
|
+
if (CODE_FENCE.test(raw)) {
|
|
99
|
+
flushTable();
|
|
100
|
+
inFence = !inFence;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (inFence) {
|
|
104
|
+
lines.push(raw);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (spoken(mode) && TABLE_ROW_LINE.test(raw)) {
|
|
108
|
+
table.push(raw);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
flushTable();
|
|
112
|
+
lines.push(readableLine(raw, mode));
|
|
113
|
+
}
|
|
114
|
+
flushTable();
|
|
115
|
+
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
116
|
+
}
|
|
117
|
+
function answerPlainText(markdown) {
|
|
118
|
+
return readableText(markdown, "clipboard");
|
|
119
|
+
}
|
|
120
|
+
function answerSpeechText(markdown) {
|
|
121
|
+
return readableText(markdown, "speech");
|
|
122
|
+
}
|
|
123
|
+
function answerDisplayText(markdown) {
|
|
124
|
+
return readableText(markdown, "display");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
exports.answerDisplayText = answerDisplayText;
|
|
128
|
+
exports.answerPlainText = answerPlainText;
|
|
129
|
+
exports.answerSpeechText = answerSpeechText;
|
|
130
|
+
exports.fill = fill;
|
|
131
|
+
//# sourceMappingURL=chunk-N7E6Z2GF.js.map
|
|
132
|
+
//# sourceMappingURL=chunk-N7E6Z2GF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/explore-fill.ts","../src/components/explore-answer-text.ts"],"names":["spoken"],"mappings":"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","file":"chunk-N7E6Z2GF.js","sourcesContent":["/**\n * Substitute `{product}` and friends without pulling in a formatting library.\n *\n * A module of its own so the live avatar's view, which is loaded on its own (see `explore-avatar-live.tsx`), can use\n * it without carrying `explore-i18n.ts`, the panel's strings. `explore-i18n.ts` re-exports it.\n */\nexport function fill(template: string, values: Record<string, string>): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key) => values[key] ?? match);\n}\n","/**\n * An answer as readable text: the words of `ExploreMessage.content` (GitHub-flavoured markdown, written by the explore\n * harness: `dev/platform/context/publishing/explore/CONTRACT.md` §7) without the markup that renders them. Three readers,\n * one implementation: the clipboard ({@link answerPlainText}), a voice ({@link answerSpeechText}), and a screen that\n * shows a voice's words as text ({@link answerDisplayText}).\n *\n * Split out of `explore-answer.tsx`, which re-exports the first two where they always were, so that a surface that\n * shows an answer as text can have them without the renderer. The third is the live avatar's view's own, and not part of\n * the package's API. The live avatar's view is a chunk of its own (BOFF-9797), and an\n * import from the renderer's module ties that chunk to the renderer's: the markdown components, the video player and\n * `react-markdown` behind them. This module imports nothing, React included.\n */\n\n/** A ``` or ~~~ fence, which opens and closes a verbatim block. */\nconst CODE_FENCE = /^\\s*(?:```|~~~)/;\n/** A GFM table row. Every row the harness writes starts with a pipe (CONTRACT.md §7). */\nconst TABLE_ROW_LINE = /^\\s{0,3}\\|/;\n/** The `| --- | :-: |` row, without which the rows around it are not a table at all. */\nconst TABLE_DELIMITER_ROW = /^\\s{0,3}\\|[\\s:|-]*-[\\s:|-]*$/;\n\n/**\n * Who the stripped text is for. The clipboard and a voice differ in exactly three places —\n * links, tables and the product-video line — and nowhere else, which is why they are one\n * function and not two. A display is a voice's words shown as text, and differs from a voice\n * in one place more: list markers.\n */\ntype ReadableFor = \"clipboard\" | \"speech\" | \"display\";\n\n/*\n * The inline patterns below. Every bracketed part is `[^[\\]]`, not `[^\\]]`: excluding `[`\n * ends each attempt at the next bracket, where `[^\\]]` ran to the end of the line and back\n * for every `[` in it — quadratic on a line of unclosed `[![`. A label with nested brackets\n * matched neither class (its first `]` is not followed by `(`), and an unclosed `[` before a\n * link is now left as the literal bracket CommonMark renders it as, not folded into the label.\n */\n\n/**\n * A link whose whole label is an image: `[](target)` — the product-video line\n * (CONTRACT.md §7) when the target is a video file, a linked illustration otherwise. Captures\n * the alt text and the target.\n */\nconst LINKED_IMAGE =\n /\\[!\\[([^[\\]]*)\\]\\([^)\\s]*(?:\\s+\"[^\"]*\")?\\)\\]\\(([^)\\s]+)(?:\\s+\"[^\"]*\")?\\)/g;\n/** ``. */\nconst IMAGE = /!\\[[^[\\]]*\\]\\([^)\\s]*(?:\\s+\"[^\"]*\")?\\)/g;\n/** `[label](href)`. Captures both. */\nconst LINK = /\\[([^[\\]]+)\\]\\(([^)\\s]+)(?:\\s+\"[^\"]*\")?\\)/g;\n/** A list item's marker, `- `, `* `, `+ `, `1. ` or `1) `. Captures the indentation before it. */\nconst LIST_MARKER = /^(\\s*)(?:[-*+]|\\d{1,9}[.)])[ \\t]+/;\n\n/** Whether `mode` is a voice's words: read out, or shown as they are read out. */\nfunction spoken(mode: ReadableFor): boolean {\n return mode !== \"clipboard\";\n}\n\n/**\n * One line with its inline markup removed. A thematic break comes back as a blank line. `item`\n * is false for a line that cannot be a list item: a table's row, made a sentence by\n * {@link spokenTable}.\n */\nfunction readableLine(raw: string, mode: ReadableFor, item = true): string {\n let line = raw;\n // Every pattern here needs `](`. Most lines of an answer have none, and skipping them is\n // what keeps a long paragraph of brackets from costing a pass per pattern.\n if (line.includes(\"](\")) {\n // A linked image before images and links, because it is both: taken apart by the two\n // rules below, the image would go as images do and leave `[](https://…)` behind, which\n // no link pattern matches — and a voice would then spell the address out. What is left\n // is a link whose label is the image's alt text: `Planmagnet product video (https://…mp4)`\n // on the clipboard, and nothing in speech, where an image is never read.\n line = line.replace(LINKED_IMAGE, (_match, alt: string, href: string) => {\n if (spoken(mode)) return \"\";\n return alt.trim() === \"\" ? href : `${alt.trim()} (${href})`;\n });\n // Images before links: `` also matches the link pattern.\n line = line.replace(IMAGE, \"\");\n line = spoken(mode)\n ? // A voice reading \"h-t-t-p-s colon slash slash…\" mid-sentence is noise, and the\n // prompt asks for two to five links in every answer. The label IS the words.\n line.replace(LINK, \"$1\")\n : line.replace(LINK, \"$1 ($2)\");\n }\n line = spoken(mode)\n ? line.replace(/<(https?:\\/\\/[^>\\s]+)>/g, \"\")\n : line.replace(/<(https?:\\/\\/[^>\\s]+)>/g, \"$1\");\n line = line.replace(/^\\s{0,3}#{1,6}\\s+/, \"\");\n line = line.replace(/^\\s{0,3}>\\s?/, \"\");\n // A thematic break is a horizontal rule; as text it is three hyphens meaning nothing.\n if (/^\\s{0,3}([-*_])[ \\t]*(?:\\1[ \\t]*){2,}$/.test(line)) return \"\";\n // Shown without a renderer, in a paragraph that keeps its line breaks, a list item is a\n // line of its own already: its marker is a renderer's mark, as `**` is. Its indentation\n // stays, so a nested item still reads as one. After the rule above: `* * *` is a rule.\n if (mode === \"display\" && item) line = line.replace(LIST_MARKER, \"$1\");\n line = line.replace(/`([^`]+)`/g, \"$1\");\n line = line.replace(/\\*\\*([^*]+)\\*\\*/g, \"$1\");\n line = line.replace(/__([^_]+)__/g, \"$1\");\n // Single-delimiter emphasis only when it wraps a run, so a `* ` bullet and a\n // snake_case_identifier are both left alone.\n line = line.replace(/(^|[^*\\w])\\*([^*\\n]+)\\*(?![*\\w])/g, \"$1$2\");\n line = line.replace(/(^|[^_\\w])_([^_\\n]+)_(?![_\\w])/g, \"$1$2\");\n return line.replace(/[ \\t]+$/, \"\");\n}\n\n/**\n * The cells of one table row, in column order.\n *\n * Split by hand rather than with a lookbehind for `\\|`: a lookbehind is a parse error on\n * Safari before 16.4, and a regex literal cannot be caught — the whole bundle fails to load.\n */\nfunction tableCells(row: string): string[] {\n const trimmed = row.trim();\n const cells: string[] = [];\n let cell = \"\";\n for (let at = 0; at < trimmed.length; at += 1) {\n const char = trimmed[at] ?? \"\";\n if (char === \"\\\\\") {\n // An escaped pipe is a pipe in the cell's text, not a column break.\n cell += char + (trimmed[at + 1] ?? \"\");\n at += 1;\n continue;\n }\n if (char === \"|\") {\n cells.push(cell.trim());\n cell = \"\";\n continue;\n }\n cell += char;\n }\n cells.push(cell.trim());\n // `| a | b |` splits to an empty element outside each edge pipe. Only empty ones go: a\n // row whose FIRST cell is genuinely blank keeps it, and so keeps its columns aligned.\n if (trimmed.startsWith(\"|\") && cells[0] === \"\") cells.shift();\n if (trimmed.endsWith(\"|\") && cells[cells.length - 1] === \"\") cells.pop();\n return cells;\n}\n\n/** `a. b` reads as one thought; `a. b.` reads as two finished ones. */\nfunction asSentence(text: string): string {\n return /[.!?]$/.test(text) ? text : `${text}.`;\n}\n\n/**\n * A table as something a voice can say: one sentence per row, every cell under its own\n * column heading, exactly as the phone layout prints it (`AnswerTableRow` in `explore-answer.tsx`,\n * `data-label`).\n * The header row itself is not spoken — it IS the labels — and neither is the `| --- |` row.\n *\n * Rows that are not a table (no delimiter row) are handed back untouched: they render as a\n * paragraph of pipes, and reading them as a table would be reading something nobody can see.\n */\nfunction spokenTable(rows: string[]): string[] {\n if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? \"\")) return rows;\n const labels = tableCells(rows[0] ?? \"\");\n const spoken: string[] = [];\n for (const row of rows.slice(2)) {\n const parts: string[] = [];\n tableCells(row).forEach((value, column) => {\n if (value === \"\") return;\n // The first cell is what the row is ABOUT — the plan, or the product. Saying \"Plan:\"\n // in front of the plan's own name adds a word and no information, which is why the\n // card layout hides that label too.\n const label = column === 0 ? \"\" : (labels[column] ?? \"\");\n parts.push(label === \"\" ? value : `${label}: ${value}`);\n });\n if (parts.length > 0) spoken.push(asSentence(parts.join(\". \")));\n }\n return spoken;\n}\n\nfunction readableText(markdown: string, mode: ReadableFor): string {\n const lines: string[] = [];\n let inFence = false;\n let table: string[] = [];\n\n const flushTable = (): void => {\n if (table.length === 0) return;\n for (const line of spokenTable(table)) {\n lines.push(readableLine(line, mode, false));\n }\n table = [];\n };\n\n for (const raw of markdown.split(\"\\n\")) {\n if (CODE_FENCE.test(raw)) {\n flushTable();\n inFence = !inFence;\n continue;\n }\n if (inFence) {\n lines.push(raw);\n continue;\n }\n // A table has to be read as a block — a cell means nothing without its column heading,\n // which is on another line. Only a voice's words need that; the clipboard wants the table.\n if (spoken(mode) && TABLE_ROW_LINE.test(raw)) {\n table.push(raw);\n continue;\n }\n flushTable();\n lines.push(readableLine(raw, mode));\n }\n flushTable();\n\n return lines\n .join(\"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\n/**\n * An answer as READABLE TEXT, for the clipboard.\n *\n * What counts as \"our internal markup artefacts\", and why each choice:\n *\n * - **Image embeds go.** `` is a picture this\n * page injected into the answer; pasted into an email it is a URL and an alt string, and\n * neither is the answer. The caption line the backend writes under it is ordinary prose and\n * stays.\n * - **Links become `label (url)`.** The destination is information the reader may need; the\n * bracket-paren syntax is not. Dropping the URL would quietly strip the page it came from.\n * - **A linked image is a link like any other**, labelled by the image's alt text — so the\n * product video is `Planmagnet product video (https://…mp4)`. The image itself goes as\n * every image does; the link is kept because, unlike a bare illustration, it is somewhere\n * the reader can GO — and a pasted answer has no card to click.\n * - **Citation markers `[3]` stay.** They are the only attribution the copied text carries,\n * and they are the answer's own — not something the UI added. The sources they point at are\n * listed right next to the button that copied this.\n * - **Emphasis, headings, blockquote and rule markers go**, because `**` and `###` are\n * instructions to a renderer, not words.\n * - **A table keeps its pipes.** Pasted into a README, an issue or a chat it is a table\n * again; flattened into sentences it is not.\n * - **Fenced code is verbatim**, fences included, minus nothing but the fence lines\n * themselves: stripping `*` or `_` inside a code block would corrupt the code.\n *\n * Built from `message.content` rather than from the rendered DOM: `textContent` on the bubble\n * would fold every paragraph, list item and table cell into one run-on line.\n */\nexport function answerPlainText(markdown: string): string {\n return readableText(markdown, \"clipboard\");\n}\n\n/**\n * The same answer as something to SAY, for read-aloud.\n *\n * It shares every rule above but the three that are right for a paste and wrong for a voice:\n *\n * - **A link is its label.** The clipboard keeps `label (https://…)` because a pasted answer\n * loses the page otherwise. Spoken, that is a URL spelled out character by character in\n * the middle of a sentence — and the prompt asks for two to five links in every answer, so\n * it is not an edge case. The sources are listed under the answer either way.\n * - **A linked image is not read at all** — the product video included. Its label is a\n * picture, which is never read, and its link is an address; the card or illustration is\n * on screen for anyone who can click it.\n * - **A table is read as rows, not as pipes.** `| --- | --- |` is a line of punctuation and\n * the pipes are column rules, neither of which is a word. Each row is read with its cells\n * under their own column headings — the phone layout, out loud — so a plan list still says\n * which number is the price. Comparisons and plan lists are exactly the answers the\n * feature was built for (CONTRACT.md §7), which is what makes this worth a second mode.\n *\n * Everything else stays deliberately identical, including citation markers: they are the\n * answer's own attribution in both, and a listener hearing \"two\" after a claim is hearing\n * the same thing a reader sees.\n */\nexport function answerSpeechText(markdown: string): string {\n return readableText(markdown, \"speech\");\n}\n\n/**\n * The same words SHOWN, where nothing renders the answer: the live avatar's view, which shows\n * it as text in a paragraph that keeps its line breaks (BOFF-9797). In the form a voice says\n * it, as the avatar's captions show an answer: a link is its label, and no address sits in\n * the middle of a sentence.\n *\n * It shares every rule of {@link answerSpeechText} and adds one: **list markers go.** On a\n * screen with no renderer, `- `, `* `, `+ ` and `1. ` are marks like `**`, and each item is a\n * line of its own without them. Neither form above drops them: pasted, the lines are a list\n * again, and Chat's read-aloud says the answer as it always has. A table's rows are sentences\n * already, and fenced code is verbatim, so neither loses anything that looks like a marker.\n */\nexport function answerDisplayText(markdown: string): string {\n return readableText(markdown, \"display\");\n}\n"]}
|
|
@@ -33,7 +33,8 @@ function liveConfigComplete(live) {
|
|
|
33
33
|
const key = live.turnstileSiteKey;
|
|
34
34
|
const allowTest = live.allowTestSiteKey;
|
|
35
35
|
const endUnattended = live.endUnattended;
|
|
36
|
-
|
|
36
|
+
const mic = live.mic;
|
|
37
|
+
return typeof live.load === "function" && typeof live.site === "string" && isAvatarSite(live.site) && typeof live.manifestVersion === "string" && isAvatarVersion(live.manifestVersion) && (key === void 0 || key === "" || typeof key === "string" && SITE_KEY.test(key)) && (allowTest === void 0 || typeof allowTest === "boolean") && (endUnattended === void 0 || typeof endUnattended === "boolean") && (mic === void 0 || typeof mic === "boolean");
|
|
37
38
|
}
|
|
38
39
|
function turnstileSiteKeyOf(live) {
|
|
39
40
|
const key = live.turnstileSiteKey;
|
|
@@ -55,5 +56,5 @@ exports.isAvatarVersion = isAvatarVersion;
|
|
|
55
56
|
exports.isRealTurnstileSiteKey = isRealTurnstileSiteKey;
|
|
56
57
|
exports.liveConfigComplete = liveConfigComplete;
|
|
57
58
|
exports.turnstileSiteKeyOf = turnstileSiteKeyOf;
|
|
58
|
-
//# sourceMappingURL=chunk-
|
|
59
|
-
//# sourceMappingURL=chunk-
|
|
59
|
+
//# sourceMappingURL=chunk-XIZE37IB.js.map
|
|
60
|
+
//# sourceMappingURL=chunk-XIZE37IB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/explore-avatar-live-config.ts"],"names":[],"mappings":";;;AAwFO,IAAM,mBAAA,GAAsB,oBAAA;AAEnC,IAAM,IAAA,GAAO,wBAAA;AACb,IAAM,OAAA,GAAU,wBAAA;AAChB,IAAM,QAAA,GAAW,wBAAA;AAKjB,IAAM,aAAA,GAAgB,wBAAA;AAMtB,IAAM,aAAA,GAAgB,0BAAA;AACtB,IAAM,iCAAiB,IAAI,GAAA,CAAI,CAAC,WAAA,EAAa,WAAA,EAAa,OAAO,CAAC,CAAA;AAG3D,SAAS,aAAa,KAAA,EAAwB;AACnD,EAAA,OAAO,IAAA,CAAK,KAAK,KAAK,CAAA;AACxB;AAGO,SAAS,gBAAgB,KAAA,EAAwB;AACtD,EAAA,OAAO,OAAA,CAAQ,KAAK,KAAK,CAAA;AAC3B;AAOO,SAAS,cAAc,UAAA,EAA+C;AAC3E,EAAA,IAAI,CAAC,YAAY,OAAO,IAAA;AACxB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAI,IAAI,UAAU,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,KAAA,GACJ,IAAI,QAAA,KAAa,OAAA,IAAW,eAAe,GAAA,CAAI,GAAA,CAAI,QAAA,CAAS,WAAA,EAAa,CAAA;AAC3E,EAAA,IAAI,GAAA,CAAI,QAAA,KAAa,QAAA,IAAY,CAAC,OAAO,OAAO,IAAA;AAChD,EAAA,OAAO,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,EAAG,mBAAmB,CAAA,CAAA;AAC5C;AAGO,SAAS,uBAAuB,GAAA,EAAsB;AAC3D,EAAA,OAAO,cAAc,IAAA,CAAK,GAAG,KAAK,CAAC,aAAA,CAAc,KAAK,GAAG,CAAA;AAC3D;AAMO,SAAS,mBAAmB,IAAA,EAAuC;AACxE,EAAA,MAAM,MAAe,IAAA,CAAK,gBAAA;AAC1B,EAAA,MAAM,YAAqB,IAAA,CAAK,gBAAA;AAChC,EAAA,MAAM,gBAAyB,IAAA,CAAK,aAAA;AACpC,EAAA,MAAM,MAAe,IAAA,CAAK,GAAA;AAC1B,EAAA,OACE,OAAO,KAAK,IAAA,KAAS,UAAA,IACrB,OAAO,IAAA,CAAK,IAAA,KAAS,YACrB,YAAA,CAAa,IAAA,CAAK,IAAI,CAAA,IACtB,OAAO,KAAK,eAAA,KAAoB,QAAA,IAChC,gBAAgB,IAAA,CAAK,eAAe,MACnC,GAAA,KAAQ,MAAA,IACP,QAAQ,EAAA,IACP,OAAO,QAAQ,QAAA,IAAY,QAAA,CAAS,KAAK,GAAG,CAAA,CAAA,KAC9C,cAAc,MAAA,IAAa,OAAO,cAAc,SAAA,CAAA,KAChD,aAAA,KAAkB,UAAa,OAAO,aAAA,KAAkB,eACxD,GAAA,KAAQ,MAAA,IAAa,OAAO,GAAA,KAAQ,SAAA,CAAA;AAEzC;AAMO,SAAS,mBACd,IAAA,EACe;AACf,EAAA,MAAM,MAAM,IAAA,CAAK,gBAAA;AACjB,EAAA,IAAI,OAAO,QAAQ,QAAA,IAAY,CAAC,SAAS,IAAA,CAAK,GAAG,GAAG,OAAO,IAAA;AAC3D,EAAA,IAAI,KAAK,gBAAA,KAAqB,KAAA,IAAS,CAAC,sBAAA,CAAuB,GAAG,CAAA,EAAG;AACnE,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA;AACT;AAMA,IAAM,QAAA,mBAAuC,IAAI,GAAA,CAAmB,CAAC,IAAI,CAAC,CAAA;AAGnE,SAAS,2BAA2B,MAAA,EAAgC;AACzE,EAAA,OAAO,QAAA,CAAS,IAAI,MAAM,CAAA;AAC5B","file":"chunk-XIZE37IB.js","sourcesContent":["/**\n * BOFF-9797. The small part of the live avatar that the panel itself needs, whether or not it is ever opened: what a\n * host's `avatar.live` is, whether the live avatar can run on this page at all, and where its requests go.\n *\n * Everything else (the view, its state machine, the requests, the words, the bot check) is a separate chunk that a\n * site only downloads when its visitor first opens the Avatar view (see `explore-avatar-live.tsx`). That is why this\n * file is separate and kept to a few lines: it is the only part of the live avatar in the package root for a site that\n * does not pass `avatar.live`, and the part of it that decides, without loading anything, whether the chunk is wanted.\n * Types only, plus plain functions: nothing here imports a component, a context or a string table.\n */\nimport type { ExploreLocale } from \"./explore-i18n\";\nimport type { AvatarLiveModule } from \"../avatar-live/types\";\nimport type { UseExploreChatResult } from \"../hooks/use-explore-chat\";\n\n/**\n * BOFF-9797. A host's opt-in to the live avatar on its panel's Avatar tab. `site`, `load` and `manifestVersion` are\n * required; a config that is incomplete, or has an optional field of the wrong shape, leaves the tab as it was (and a\n * warning in the console).\n */\nexport interface ExplorePanelAvatarLive {\n /** The site, as the live avatar service knows it: `healthybowl`. */\n site: string;\n /**\n * Loads the live avatar's room module: `() => import('@burdenoff/website-sdk/avatar-live')`. Called only after the\n * visitor presses Start on the consent card, never on page load or on opening the tab. That entry needs the\n * package's optional peer dependency for media rooms (see the README).\n */\n load: () => Promise<AvatarLiveModule>;\n /**\n * The site's Cloudflare Turnstile site key, for the bot check that runs when the visitor presses Start. Used only when\n * the server says it checks for bots (its availability's `botCheck`, contract v1.2), or says nothing of it (a server\n * older than v1.2, which always checks): such a server's live avatar then needs a key, and without one the tab stays\n * as it was. A server that runs no bot check needs none, and no Cloudflare script is loaded for it, key or not.\n */\n turnstileSiteKey?: string;\n /**\n * Whether `turnstileSiteKey` may be one of Cloudflare's test keys (`1x00000000000000000000AA` and keys of their\n * shape). `websiteAvatarLive` sets it: true on alpha only, false everywhere else, so a production page never runs a\n * test widget. Unset (a config made by hand): any key of a valid shape, as before.\n */\n allowTestSiteKey?: boolean;\n /** The version of the suggested-question list the panel shows (its `examplePrompts`), such as `hb-en-1`. */\n manifestVersion: string;\n /**\n * End a session nobody is attending to (BOFF-9797 stage A). A session keeps playing behind the Chat tab and on a\n * hidden page, so the answer under way is heard to its end; with this set, out of sight it also ends:\n *\n * - **once its answer has played out**, at once: the idle tail after an answer runs only while the Avatar view is in\n * sight (the shared lifecycle rule; see `AvatarLiveView`'s `attend`);\n * - **when the browser holds its sound back** (autoplay refused, or the audio context suspended), after a short grace:\n * the \"Tap to turn on sound\" button that would let it play is out of sight, so nobody hears it.\n *\n * Both end it with reason `user`. Unset or false: such a session plays on to the end the server planned for it, as\n * before.\n */\n endUnattended?: boolean;\n /**\n * Offer a mic in the Avatar tab's own question box (BOFF-9797 Stage C): tap to start, tap to stop, up to thirty\n * seconds, the text into the box for the visitor to correct and Send (nothing is sent by itself). Offered only where\n * the server's availability answer also carries a mic offer (`stt`, while the gateway's website speech-to-text is on)\n * and the box takes a question, after a mic notice of its own on the first tap. The mic's code is a chunk of its own,\n * loaded the first time it is used. Unset or false: no mic, as before.\n */\n mic?: boolean;\n}\n\n/**\n * What the live avatar reads of the panel's conversation: the transcript, whether it is read-only and the token, and\n * `reset` (the chat's own New chat) for its New chat button where the conversation can never be read. Where the server\n * reads typed questions too (contract v1.3), the Avatar tab's own question box asks through the same conversation:\n * `send` (with `surface: \"AVATAR\"`), and `canSend`, `phase`, `activity` and `catalog` to say when it can and what the\n * answer is doing, and `resent` to follow a question asked there that Chat's Retry sent again.\n */\nexport type AvatarConversation = Pick<\n UseExploreChatResult,\n | \"messages\"\n | \"isSharedView\"\n | \"getConversationToken\"\n | \"reset\"\n | \"send\"\n | \"canSend\"\n | \"phase\"\n | \"activity\"\n | \"catalog\"\n | \"resent\"\n>;\n\n/** The web branch's routes, under the gateway's origin. */\nexport const AVATAR_WEB_API_PATH = \"/api/web-avatar/v1\";\n\nconst SITE = /^[a-z][a-z0-9-]{1,31}$/;\nconst VERSION = /^[A-Za-z0-9._-]{1,32}$/;\nconst SITE_KEY = /^[A-Za-z0-9_-]{1,128}$/;\n/**\n * The shape of Cloudflare's dummy site keys: a digit, `x`, a run of zeros and two letters (`1x00000000000000000000AA`,\n * `2x00000000000000000000AB`, `3x00000000000000000000FF`, …). Also catches an all-zero `0x…` placeholder.\n */\nconst TEST_SITE_KEY = /^\\dx0{16,}[A-Za-z]{2}$/;\n/**\n * A real-looking site key. Cloudflare documents no format for real keys; every key it issues starts `0x` (in practice\n * `0x4AAAAAAA…`, about 24 characters), in letters, digits, `_` and `-`. The length is kept loose (18 to 64 in all) so a\n * longer key Cloudflare might issue later still passes, and the charset matches what the panel accepts.\n */\nconst REAL_SITE_KEY = /^0x[A-Za-z0-9_-]{16,62}$/;\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n\n/** A site slug the contract accepts (`healthybowl`). */\nexport function isAvatarSite(value: string): boolean {\n return SITE.test(value);\n}\n\n/** A consent or manifest version the contract accepts (`hb-en-1`). */\nexport function isAvatarVersion(value: string): boolean {\n return VERSION.test(value);\n}\n\n/**\n * The web branch's base URL on the SDK's configured gateway: the gateway URL's origin, plus\n * {@link AVATAR_WEB_API_PATH}. Null for anything that is not an `https:` URL (or `http:` on this machine, for local\n * development).\n */\nexport function avatarApiBase(gatewayUrl: string | undefined): string | null {\n if (!gatewayUrl) return null;\n let url: URL;\n try {\n url = new URL(gatewayUrl);\n } catch {\n return null;\n }\n const local =\n url.protocol === \"http:\" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase());\n if (url.protocol !== \"https:\" && !local) return null;\n return `${url.origin}${AVATAR_WEB_API_PATH}`;\n}\n\n/** A real-looking Turnstile site key: `0x…`, and not of the shape of Cloudflare's test keys. */\nexport function isRealTurnstileSiteKey(key: string): boolean {\n return REAL_SITE_KEY.test(key) && !TEST_SITE_KEY.test(key);\n}\n\n/**\n * Whether a host's `avatar.live` has everything the live avatar needs, in the shapes the contract accepts. The site\n * key is optional (an empty one is none), and checked only when it is there.\n */\nexport function liveConfigComplete(live: ExplorePanelAvatarLive): boolean {\n const key: unknown = live.turnstileSiteKey;\n const allowTest: unknown = live.allowTestSiteKey;\n const endUnattended: unknown = live.endUnattended;\n const mic: unknown = live.mic;\n return (\n typeof live.load === \"function\" &&\n typeof live.site === \"string\" &&\n isAvatarSite(live.site) &&\n typeof live.manifestVersion === \"string\" &&\n isAvatarVersion(live.manifestVersion) &&\n (key === undefined ||\n key === \"\" ||\n (typeof key === \"string\" && SITE_KEY.test(key))) &&\n (allowTest === undefined || typeof allowTest === \"boolean\") &&\n (endUnattended === undefined || typeof endUnattended === \"boolean\") &&\n (mic === undefined || typeof mic === \"boolean\")\n );\n}\n\n/**\n * The site key a bot check at Start may use, or null when there is none: no key, one of no valid shape, or one of\n * Cloudflare's test keys where `allowTestSiteKey` is false.\n */\nexport function turnstileSiteKeyOf(\n live: ExplorePanelAvatarLive,\n): string | null {\n const key = live.turnstileSiteKey;\n if (typeof key !== \"string\" || !SITE_KEY.test(key)) return null;\n if (live.allowTestSiteKey === false && !isRealTurnstileSiteKey(key)) {\n return null;\n }\n return key;\n}\n\n/**\n * The locales whose live-avatar strings have been reviewed. The live avatar runs only in these; see\n * `explore-avatar-i18n.ts`. English only, until a named reviewer signs off on another.\n */\nconst REVIEWED: ReadonlySet<ExploreLocale> = new Set<ExploreLocale>([\"en\"]);\n\n/** Whether the live avatar may run in `locale`: its strings have been reviewed. */\nexport function isAvatarLiveLocaleReviewed(locale: ExploreLocale): boolean {\n return REVIEWED.has(locale);\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import 'react';
|
|
2
|
-
export { a as EXPLORE_PREFILL_MAX_LENGTH, e as ExplorePage, f as ExplorePageProps, m as ExploreProvenance, n as ExploreReferenceKind, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, o as logoUrlOf, q as productNamesByOrigin, r as referenceKindOf, s as summariseReferences } from '../explore-
|
|
2
|
+
export { a as EXPLORE_PREFILL_MAX_LENGTH, e as ExplorePage, f as ExplorePageProps, m as ExploreProvenance, n as ExploreReferenceKind, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, o as logoUrlOf, q as productNamesByOrigin, r as referenceKindOf, s as summariseReferences } from '../explore-ei8uoko9.mjs';
|
|
3
3
|
import '../explore-types-Ol1Mm3dW.mjs';
|
|
4
4
|
import 'react-markdown';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import 'react';
|
|
2
|
-
export { a as EXPLORE_PREFILL_MAX_LENGTH, e as ExplorePage, f as ExplorePageProps, m as ExploreProvenance, n as ExploreReferenceKind, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, o as logoUrlOf, q as productNamesByOrigin, r as referenceKindOf, s as summariseReferences } from '../explore-
|
|
2
|
+
export { a as EXPLORE_PREFILL_MAX_LENGTH, e as ExplorePage, f as ExplorePageProps, m as ExploreProvenance, n as ExploreReferenceKind, g as answerPlainText, h as answerSpeechText, i as exploreQuestionHref, j as inlineImageOrigins, k as isInlineImageAllowed, l as isInlineVideoAllowed, o as logoUrlOf, q as productNamesByOrigin, r as referenceKindOf, s as summariseReferences } from '../explore-Dfru4j4m.js';
|
|
3
3
|
import '../explore-types-Ol1Mm3dW.js';
|
|
4
4
|
import 'react-markdown';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
require('../chunk-
|
|
3
|
+
var chunkMBVXDFVC_js = require('../chunk-MBVXDFVC.js');
|
|
4
|
+
var chunkN7E6Z2GF_js = require('../chunk-N7E6Z2GF.js');
|
|
5
5
|
require('../chunk-P5GMVPR6.js');
|
|
6
6
|
require('../chunk-PHXAGQNB.js');
|
|
7
7
|
require('../chunk-3LFFY7AZ.js');
|
|
@@ -18,51 +18,51 @@ require('../chunk-ZSTFX7FV.js');
|
|
|
18
18
|
|
|
19
19
|
Object.defineProperty(exports, "EXPLORE_PREFILL_MAX_LENGTH", {
|
|
20
20
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
21
|
+
get: function () { return chunkMBVXDFVC_js.EXPLORE_PREFILL_MAX_LENGTH; }
|
|
22
22
|
});
|
|
23
23
|
Object.defineProperty(exports, "ExplorePage", {
|
|
24
24
|
enumerable: true,
|
|
25
|
-
get: function () { return
|
|
26
|
-
});
|
|
27
|
-
Object.defineProperty(exports, "answerPlainText", {
|
|
28
|
-
enumerable: true,
|
|
29
|
-
get: function () { return chunk22OT6E26_js.answerPlainText; }
|
|
30
|
-
});
|
|
31
|
-
Object.defineProperty(exports, "answerSpeechText", {
|
|
32
|
-
enumerable: true,
|
|
33
|
-
get: function () { return chunk22OT6E26_js.answerSpeechText; }
|
|
25
|
+
get: function () { return chunkMBVXDFVC_js.ExplorePage; }
|
|
34
26
|
});
|
|
35
27
|
Object.defineProperty(exports, "exploreQuestionHref", {
|
|
36
28
|
enumerable: true,
|
|
37
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunkMBVXDFVC_js.exploreQuestionHref; }
|
|
38
30
|
});
|
|
39
31
|
Object.defineProperty(exports, "inlineImageOrigins", {
|
|
40
32
|
enumerable: true,
|
|
41
|
-
get: function () { return
|
|
33
|
+
get: function () { return chunkMBVXDFVC_js.inlineImageOrigins; }
|
|
42
34
|
});
|
|
43
35
|
Object.defineProperty(exports, "isInlineImageAllowed", {
|
|
44
36
|
enumerable: true,
|
|
45
|
-
get: function () { return
|
|
37
|
+
get: function () { return chunkMBVXDFVC_js.isInlineImageAllowed; }
|
|
46
38
|
});
|
|
47
39
|
Object.defineProperty(exports, "isInlineVideoAllowed", {
|
|
48
40
|
enumerable: true,
|
|
49
|
-
get: function () { return
|
|
41
|
+
get: function () { return chunkMBVXDFVC_js.isInlineVideoAllowed; }
|
|
50
42
|
});
|
|
51
43
|
Object.defineProperty(exports, "logoUrlOf", {
|
|
52
44
|
enumerable: true,
|
|
53
|
-
get: function () { return
|
|
45
|
+
get: function () { return chunkMBVXDFVC_js.logoUrlOf; }
|
|
54
46
|
});
|
|
55
47
|
Object.defineProperty(exports, "productNamesByOrigin", {
|
|
56
48
|
enumerable: true,
|
|
57
|
-
get: function () { return
|
|
49
|
+
get: function () { return chunkMBVXDFVC_js.productNamesByOrigin; }
|
|
58
50
|
});
|
|
59
51
|
Object.defineProperty(exports, "referenceKindOf", {
|
|
60
52
|
enumerable: true,
|
|
61
|
-
get: function () { return
|
|
53
|
+
get: function () { return chunkMBVXDFVC_js.referenceKindOf; }
|
|
62
54
|
});
|
|
63
55
|
Object.defineProperty(exports, "summariseReferences", {
|
|
64
56
|
enumerable: true,
|
|
65
|
-
get: function () { return
|
|
57
|
+
get: function () { return chunkMBVXDFVC_js.summariseReferences; }
|
|
58
|
+
});
|
|
59
|
+
Object.defineProperty(exports, "answerPlainText", {
|
|
60
|
+
enumerable: true,
|
|
61
|
+
get: function () { return chunkN7E6Z2GF_js.answerPlainText; }
|
|
62
|
+
});
|
|
63
|
+
Object.defineProperty(exports, "answerSpeechText", {
|
|
64
|
+
enumerable: true,
|
|
65
|
+
get: function () { return chunkN7E6Z2GF_js.answerSpeechText; }
|
|
66
66
|
});
|
|
67
67
|
//# sourceMappingURL=explore.js.map
|
|
68
68
|
//# sourceMappingURL=explore.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { EXPLORE_PREFILL_MAX_LENGTH, ExplorePage,
|
|
2
|
-
|
|
1
|
+
export { EXPLORE_PREFILL_MAX_LENGTH, ExplorePage, exploreQuestionHref, inlineImageOrigins, isInlineImageAllowed, isInlineVideoAllowed, logoUrlOf, productNamesByOrigin, referenceKindOf, summariseReferences } from '../chunk-7HE33NBS.mjs';
|
|
2
|
+
export { answerPlainText, answerSpeechText } from '../chunk-JZS6FQT6.mjs';
|
|
3
3
|
import '../chunk-SMD7MEEA.mjs';
|
|
4
4
|
import '../chunk-SKIYDKIS.mjs';
|
|
5
5
|
import '../chunk-6VJT3SM4.mjs';
|
|
@@ -14,39 +14,17 @@ interface ExploreVideoStrings {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Origins an answer may embed images from: every catalog product's website, apex and
|
|
23
|
-
* `www.` alike (BotManor ships on www.botmanor.com, everything else on the apex, and either can
|
|
24
|
-
* appear in the corpus). https only.
|
|
25
|
-
*/
|
|
26
|
-
declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
|
|
27
|
-
/**
|
|
28
|
-
* True when `src` is an https URL on a catalog product site. The backend already strips
|
|
29
|
-
* every image it did not put in the answer; this is the browser's own check, so a model
|
|
30
|
-
* that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
|
|
31
|
-
*
|
|
32
|
-
* A video or caption file is refused even on a catalog site: as an `<img src>` it downloads
|
|
33
|
-
* video bytes into a transcript that promises never to preload one (§7), and draws nothing.
|
|
34
|
-
* That covers a product-video card's poster as well as a plain ``.
|
|
35
|
-
*/
|
|
36
|
-
declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
|
|
37
|
-
/**
|
|
38
|
-
* True when `href` may become a product-video card: the SAME origin rule as an inline image
|
|
39
|
-
* (https, no credentials, a catalog product site), and a path that is a playable video file.
|
|
40
|
-
*/
|
|
41
|
-
declare function isInlineVideoAllowed(href: unknown, origins: ReadonlySet<string>): href is string;
|
|
42
|
-
/**
|
|
43
|
-
* Apex origin → catalog slug, so a video card can say WHICH product it is (`data-product`).
|
|
17
|
+
* An answer as readable text: the words of `ExploreMessage.content` (GitHub-flavoured markdown, written by the explore
|
|
18
|
+
* harness: `dev/platform/context/publishing/explore/CONTRACT.md` §7) without the markup that renders them. Three readers,
|
|
19
|
+
* one implementation: the clipboard ({@link answerPlainText}), a voice ({@link answerSpeechText}), and a screen that
|
|
20
|
+
* shows a voice's words as text ({@link answerDisplayText}).
|
|
44
21
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
22
|
+
* Split out of `explore-answer.tsx`, which re-exports the first two where they always were, so that a surface that
|
|
23
|
+
* shows an answer as text can have them without the renderer. The third is the live avatar's view's own, and not part of
|
|
24
|
+
* the package's API. The live avatar's view is a chunk of its own (BOFF-9797), and an
|
|
25
|
+
* import from the renderer's module ties that chunk to the renderer's: the markdown components, the video player and
|
|
26
|
+
* `react-markdown` behind them. This module imports nothing, React included.
|
|
48
27
|
*/
|
|
49
|
-
declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
|
|
50
28
|
/**
|
|
51
29
|
* An answer as READABLE TEXT, for the clipboard.
|
|
52
30
|
*
|
|
@@ -99,6 +77,42 @@ declare function answerPlainText(markdown: string): string;
|
|
|
99
77
|
* the same thing a reader sees.
|
|
100
78
|
*/
|
|
101
79
|
declare function answerSpeechText(markdown: string): string;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The answer stylesheet as an element. Safe to mount more than once — the rules are
|
|
83
|
+
* idempotent — so a page that shows answers and nothing else still gets them.
|
|
84
|
+
*/
|
|
85
|
+
declare function ExploreAnswerStyles(): react.JSX.Element;
|
|
86
|
+
/**
|
|
87
|
+
* Origins an answer may embed images from: every catalog product's website, apex and
|
|
88
|
+
* `www.` alike (BotManor ships on www.botmanor.com, everything else on the apex, and either can
|
|
89
|
+
* appear in the corpus). https only.
|
|
90
|
+
*/
|
|
91
|
+
declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
|
|
92
|
+
/**
|
|
93
|
+
* True when `src` is an https URL on a catalog product site. The backend already strips
|
|
94
|
+
* every image it did not put in the answer; this is the browser's own check, so a model
|
|
95
|
+
* that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
|
|
96
|
+
*
|
|
97
|
+
* A video or caption file is refused even on a catalog site: as an `<img src>` it downloads
|
|
98
|
+
* video bytes into a transcript that promises never to preload one (§7), and draws nothing.
|
|
99
|
+
* That covers a product-video card's poster as well as a plain ``.
|
|
100
|
+
*/
|
|
101
|
+
declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
|
|
102
|
+
/**
|
|
103
|
+
* True when `href` may become a product-video card: the SAME origin rule as an inline image
|
|
104
|
+
* (https, no credentials, a catalog product site), and a path that is a playable video file.
|
|
105
|
+
*/
|
|
106
|
+
declare function isInlineVideoAllowed(href: unknown, origins: ReadonlySet<string>): href is string;
|
|
107
|
+
/**
|
|
108
|
+
* Apex origin → catalog slug, so a video card can say WHICH product it is (`data-product`).
|
|
109
|
+
*
|
|
110
|
+
* The file's host is not the answer on its own: HealthyBowl ships on gethealthybowl.com and
|
|
111
|
+
* Workspaces on burdenoff.com, and an e2e selector written against the slug would miss both.
|
|
112
|
+
* First entry wins, as in `productNamesByOrigin`.
|
|
113
|
+
*/
|
|
114
|
+
declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
|
|
115
|
+
|
|
102
116
|
/**
|
|
103
117
|
* The strings the markdown overrides render.
|
|
104
118
|
*
|
package/dist/{explore-avatar-live-config-DRpS-mqQ.d.ts → explore-avatar-live-config-C-OwXNje.d.ts}
RENAMED
|
@@ -301,6 +301,14 @@ interface ExplorePanelAvatarLive {
|
|
|
301
301
|
* before.
|
|
302
302
|
*/
|
|
303
303
|
endUnattended?: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Offer a mic in the Avatar tab's own question box (BOFF-9797 Stage C): tap to start, tap to stop, up to thirty
|
|
306
|
+
* seconds, the text into the box for the visitor to correct and Send (nothing is sent by itself). Offered only where
|
|
307
|
+
* the server's availability answer also carries a mic offer (`stt`, while the gateway's website speech-to-text is on)
|
|
308
|
+
* and the box takes a question, after a mic notice of its own on the first tap. The mic's code is a chunk of its own,
|
|
309
|
+
* loaded the first time it is used. Unset or false: no mic, as before.
|
|
310
|
+
*/
|
|
311
|
+
mic?: boolean;
|
|
304
312
|
}
|
|
305
313
|
/**
|
|
306
314
|
* What the live avatar reads of the panel's conversation: the transcript, whether it is read-only and the token, and
|
package/dist/{explore-avatar-live-config-B6_p0q1c.d.mts → explore-avatar-live-config-OO0hvQCK.d.mts}
RENAMED
|
@@ -301,6 +301,14 @@ interface ExplorePanelAvatarLive {
|
|
|
301
301
|
* before.
|
|
302
302
|
*/
|
|
303
303
|
endUnattended?: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Offer a mic in the Avatar tab's own question box (BOFF-9797 Stage C): tap to start, tap to stop, up to thirty
|
|
306
|
+
* seconds, the text into the box for the visitor to correct and Send (nothing is sent by itself). Offered only where
|
|
307
|
+
* the server's availability answer also carries a mic offer (`stt`, while the gateway's website speech-to-text is on)
|
|
308
|
+
* and the box takes a question, after a mic notice of its own on the first tap. The mic's code is a chunk of its own,
|
|
309
|
+
* loaded the first time it is used. Unset or false: no mic, as before.
|
|
310
|
+
*/
|
|
311
|
+
mic?: boolean;
|
|
304
312
|
}
|
|
305
313
|
/**
|
|
306
314
|
* What the live avatar reads of the panel's conversation: the transcript, whether it is read-only and the token, and
|
|
@@ -14,39 +14,17 @@ interface ExploreVideoStrings {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Origins an answer may embed images from: every catalog product's website, apex and
|
|
23
|
-
* `www.` alike (BotManor ships on www.botmanor.com, everything else on the apex, and either can
|
|
24
|
-
* appear in the corpus). https only.
|
|
25
|
-
*/
|
|
26
|
-
declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
|
|
27
|
-
/**
|
|
28
|
-
* True when `src` is an https URL on a catalog product site. The backend already strips
|
|
29
|
-
* every image it did not put in the answer; this is the browser's own check, so a model
|
|
30
|
-
* that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
|
|
31
|
-
*
|
|
32
|
-
* A video or caption file is refused even on a catalog site: as an `<img src>` it downloads
|
|
33
|
-
* video bytes into a transcript that promises never to preload one (§7), and draws nothing.
|
|
34
|
-
* That covers a product-video card's poster as well as a plain ``.
|
|
35
|
-
*/
|
|
36
|
-
declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
|
|
37
|
-
/**
|
|
38
|
-
* True when `href` may become a product-video card: the SAME origin rule as an inline image
|
|
39
|
-
* (https, no credentials, a catalog product site), and a path that is a playable video file.
|
|
40
|
-
*/
|
|
41
|
-
declare function isInlineVideoAllowed(href: unknown, origins: ReadonlySet<string>): href is string;
|
|
42
|
-
/**
|
|
43
|
-
* Apex origin → catalog slug, so a video card can say WHICH product it is (`data-product`).
|
|
17
|
+
* An answer as readable text: the words of `ExploreMessage.content` (GitHub-flavoured markdown, written by the explore
|
|
18
|
+
* harness: `dev/platform/context/publishing/explore/CONTRACT.md` §7) without the markup that renders them. Three readers,
|
|
19
|
+
* one implementation: the clipboard ({@link answerPlainText}), a voice ({@link answerSpeechText}), and a screen that
|
|
20
|
+
* shows a voice's words as text ({@link answerDisplayText}).
|
|
44
21
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
22
|
+
* Split out of `explore-answer.tsx`, which re-exports the first two where they always were, so that a surface that
|
|
23
|
+
* shows an answer as text can have them without the renderer. The third is the live avatar's view's own, and not part of
|
|
24
|
+
* the package's API. The live avatar's view is a chunk of its own (BOFF-9797), and an
|
|
25
|
+
* import from the renderer's module ties that chunk to the renderer's: the markdown components, the video player and
|
|
26
|
+
* `react-markdown` behind them. This module imports nothing, React included.
|
|
48
27
|
*/
|
|
49
|
-
declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
|
|
50
28
|
/**
|
|
51
29
|
* An answer as READABLE TEXT, for the clipboard.
|
|
52
30
|
*
|
|
@@ -99,6 +77,42 @@ declare function answerPlainText(markdown: string): string;
|
|
|
99
77
|
* the same thing a reader sees.
|
|
100
78
|
*/
|
|
101
79
|
declare function answerSpeechText(markdown: string): string;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The answer stylesheet as an element. Safe to mount more than once — the rules are
|
|
83
|
+
* idempotent — so a page that shows answers and nothing else still gets them.
|
|
84
|
+
*/
|
|
85
|
+
declare function ExploreAnswerStyles(): react.JSX.Element;
|
|
86
|
+
/**
|
|
87
|
+
* Origins an answer may embed images from: every catalog product's website, apex and
|
|
88
|
+
* `www.` alike (BotManor ships on www.botmanor.com, everything else on the apex, and either can
|
|
89
|
+
* appear in the corpus). https only.
|
|
90
|
+
*/
|
|
91
|
+
declare function inlineImageOrigins(products: readonly Pick<ExploreProduct, "website">[]): Set<string>;
|
|
92
|
+
/**
|
|
93
|
+
* True when `src` is an https URL on a catalog product site. The backend already strips
|
|
94
|
+
* every image it did not put in the answer; this is the browser's own check, so a model
|
|
95
|
+
* that slips an arbitrary URL past it still cannot make the visitor's browser fetch it.
|
|
96
|
+
*
|
|
97
|
+
* A video or caption file is refused even on a catalog site: as an `<img src>` it downloads
|
|
98
|
+
* video bytes into a transcript that promises never to preload one (§7), and draws nothing.
|
|
99
|
+
* That covers a product-video card's poster as well as a plain ``.
|
|
100
|
+
*/
|
|
101
|
+
declare function isInlineImageAllowed(src: unknown, origins: ReadonlySet<string>): src is string;
|
|
102
|
+
/**
|
|
103
|
+
* True when `href` may become a product-video card: the SAME origin rule as an inline image
|
|
104
|
+
* (https, no credentials, a catalog product site), and a path that is a playable video file.
|
|
105
|
+
*/
|
|
106
|
+
declare function isInlineVideoAllowed(href: unknown, origins: ReadonlySet<string>): href is string;
|
|
107
|
+
/**
|
|
108
|
+
* Apex origin → catalog slug, so a video card can say WHICH product it is (`data-product`).
|
|
109
|
+
*
|
|
110
|
+
* The file's host is not the answer on its own: HealthyBowl ships on gethealthybowl.com and
|
|
111
|
+
* Workspaces on burdenoff.com, and an e2e selector written against the slug would miss both.
|
|
112
|
+
* First entry wins, as in `productNamesByOrigin`.
|
|
113
|
+
*/
|
|
114
|
+
declare function productSlugsByOrigin(products: readonly Pick<ExploreProduct, "slug" | "website">[]): Map<string, string>;
|
|
115
|
+
|
|
102
116
|
/**
|
|
103
117
|
* The strings the markdown overrides render.
|
|
104
118
|
*
|