@burdenoff/website-sdk 2026.1102.2 → 2026.1104.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 +63 -8
- package/dist/avatar-live-view/index.d.mts +29 -5
- package/dist/avatar-live-view/index.d.ts +29 -5
- package/dist/avatar-live-view/index.js +1319 -271
- package/dist/avatar-live-view/index.js.map +1 -1
- package/dist/avatar-live-view/index.mjs +1317 -269
- package/dist/avatar-live-view/index.mjs.map +1 -1
- package/dist/{chunk-R742FS5E.js → chunk-22OT6E26.js} +61 -27
- package/dist/chunk-22OT6E26.js.map +1 -0
- package/dist/{chunk-ETI2HQEC.mjs → chunk-5JQGE3XI.mjs} +46 -14
- package/dist/chunk-5JQGE3XI.mjs.map +1 -0
- package/dist/{chunk-FQH3AJN3.js → chunk-7O2I32ZN.js} +2 -2
- package/dist/chunk-7O2I32ZN.js.map +1 -0
- package/dist/{chunk-SFT3WHN5.mjs → chunk-K3QZGJEH.mjs} +2 -2
- package/dist/chunk-K3QZGJEH.mjs.map +1 -0
- package/dist/{chunk-SK5UGG6A.js → chunk-P5GMVPR6.js} +76 -20
- package/dist/chunk-P5GMVPR6.js.map +1 -0
- package/dist/{chunk-EUPFS6MZ.mjs → chunk-SMD7MEEA.mjs} +76 -20
- package/dist/chunk-SMD7MEEA.mjs.map +1 -0
- package/dist/components/explore.d.mts +2 -2
- package/dist/components/explore.d.ts +2 -2
- package/dist/components/explore.js +14 -14
- package/dist/components/explore.mjs +2 -2
- package/dist/{explore-DUTNYUOm.d.mts → explore-BV4dRwoC.d.ts} +8 -1
- package/dist/{explore-DjQE7Xia.d.ts → explore-BgEeGPvY.d.mts} +8 -1
- package/dist/{explore-avatar-live-config-Cetx8-Zu.d.ts → explore-avatar-live-config-CAfN5eAX.d.ts} +6 -3
- package/dist/{explore-avatar-live-config-Bp3kQbU2.d.mts → explore-avatar-live-config-N4WhJzgS.d.mts} +6 -3
- package/dist/{explore-types-Bb2Ih3d8.d.mts → explore-types-Ol1Mm3dW.d.mts} +12 -1
- package/dist/{explore-types-Bb2Ih3d8.d.ts → explore-types-Ol1Mm3dW.d.ts} +12 -1
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +9 -9
- package/dist/hooks/index.mjs +1 -1
- package/dist/index.d.mts +14 -8
- package/dist/index.d.ts +14 -8
- package/dist/index.js +100 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -6
- package/dist/index.mjs.map +1 -1
- package/dist/{use-explore-chat-DunjG-3p.d.ts → use-explore-chat-B3dOOvJN.d.ts} +43 -3
- package/dist/{use-explore-chat-zRuoAin6.d.mts → use-explore-chat-CbfohA9O.d.mts} +43 -3
- package/package.json +1 -1
- package/dist/chunk-ETI2HQEC.mjs.map +0 -1
- package/dist/chunk-EUPFS6MZ.mjs.map +0 -1
- package/dist/chunk-FQH3AJN3.js.map +0 -1
- package/dist/chunk-R742FS5E.js.map +0 -1
- package/dist/chunk-SFT3WHN5.mjs.map +0 -1
- package/dist/chunk-SK5UGG6A.js.map +0 -1
|
@@ -54,5 +54,5 @@ exports.isAvatarVersion = isAvatarVersion;
|
|
|
54
54
|
exports.isRealTurnstileSiteKey = isRealTurnstileSiteKey;
|
|
55
55
|
exports.liveConfigComplete = liveConfigComplete;
|
|
56
56
|
exports.turnstileSiteKeyOf = turnstileSiteKeyOf;
|
|
57
|
-
//# sourceMappingURL=chunk-
|
|
58
|
-
//# sourceMappingURL=chunk-
|
|
57
|
+
//# sourceMappingURL=chunk-7O2I32ZN.js.map
|
|
58
|
+
//# sourceMappingURL=chunk-7O2I32ZN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/explore-avatar-live-config.ts"],"names":[],"mappings":";;;AAmEO,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,OACE,OAAO,IAAA,CAAK,IAAA,KAAS,UAAA,IACrB,OAAO,IAAA,CAAK,IAAA,KAAS,QAAA,IACrB,YAAA,CAAa,KAAK,IAAI,CAAA,IACtB,OAAO,IAAA,CAAK,oBAAoB,QAAA,IAChC,eAAA,CAAgB,IAAA,CAAK,eAAe,CAAA,KACnC,GAAA,KAAQ,MAAA,IACP,GAAA,KAAQ,MACP,OAAO,GAAA,KAAQ,QAAA,IAAY,QAAA,CAAS,KAAK,GAAG,CAAA,CAAA,KAC9C,SAAA,KAAc,MAAA,IAAa,OAAO,SAAA,KAAc,SAAA,CAAA;AAErD;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-7O2I32ZN.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\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 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 );\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"]}
|
|
@@ -46,5 +46,5 @@ function isAvatarLiveLocaleReviewed(locale) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export { avatarApiBase, isAvatarLiveLocaleReviewed, isAvatarSite, isAvatarVersion, isRealTurnstileSiteKey, liveConfigComplete, turnstileSiteKeyOf };
|
|
49
|
-
//# sourceMappingURL=chunk-
|
|
50
|
-
//# sourceMappingURL=chunk-
|
|
49
|
+
//# sourceMappingURL=chunk-K3QZGJEH.mjs.map
|
|
50
|
+
//# sourceMappingURL=chunk-K3QZGJEH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/explore-avatar-live-config.ts"],"names":[],"mappings":";AAmEO,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,OACE,OAAO,IAAA,CAAK,IAAA,KAAS,UAAA,IACrB,OAAO,IAAA,CAAK,IAAA,KAAS,QAAA,IACrB,YAAA,CAAa,KAAK,IAAI,CAAA,IACtB,OAAO,IAAA,CAAK,oBAAoB,QAAA,IAChC,eAAA,CAAgB,IAAA,CAAK,eAAe,CAAA,KACnC,GAAA,KAAQ,MAAA,IACP,GAAA,KAAQ,MACP,OAAO,GAAA,KAAQ,QAAA,IAAY,QAAA,CAAS,KAAK,GAAG,CAAA,CAAA,KAC9C,SAAA,KAAc,MAAA,IAAa,OAAO,SAAA,KAAc,SAAA,CAAA;AAErD;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-K3QZGJEH.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\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 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 );\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"]}
|
|
@@ -748,6 +748,7 @@ function useExploreChat(options = {}) {
|
|
|
748
748
|
const [remainingToday, setRemainingToday] = react.useState(null);
|
|
749
749
|
const [focusProduct, setFocusProductState] = react.useState(null);
|
|
750
750
|
const [turnCount, setTurnCount] = react.useState(0);
|
|
751
|
+
const [resent, setResent] = react.useState(null);
|
|
751
752
|
const messagesRef = react.useRef([]);
|
|
752
753
|
const [archive, setArchive] = react.useState([]);
|
|
753
754
|
const [hydrated, setHydrated] = react.useState(false);
|
|
@@ -768,6 +769,7 @@ function useExploreChat(options = {}) {
|
|
|
768
769
|
const lastSentAttachmentsRef = react.useRef(
|
|
769
770
|
[]
|
|
770
771
|
);
|
|
772
|
+
const lastSentSurfaceRef = react.useRef(null);
|
|
771
773
|
const inFlightRef = react.useRef(false);
|
|
772
774
|
catalogRef.current = catalog;
|
|
773
775
|
const rememberConversationToken = react.useCallback((token) => {
|
|
@@ -1016,7 +1018,7 @@ function useExploreChat(options = {}) {
|
|
|
1016
1018
|
[client, pollIntervalMs, pollTimeoutMs]
|
|
1017
1019
|
);
|
|
1018
1020
|
const submit = react.useCallback(
|
|
1019
|
-
async (text, optimisticId, captchaAttempt, attachments) => {
|
|
1021
|
+
async (text, optimisticId, captchaAttempt, attachments, surface, replaces) => {
|
|
1020
1022
|
const activeCatalog = catalogRef.current;
|
|
1021
1023
|
let recaptchaToken;
|
|
1022
1024
|
if (activeCatalog.captchaRequired && activeCatalog.recaptchaSiteKey) {
|
|
@@ -1035,27 +1037,27 @@ function useExploreChat(options = {}) {
|
|
|
1035
1037
|
...recaptchaToken ? { recaptchaToken } : {},
|
|
1036
1038
|
...locale ? { locale } : {},
|
|
1037
1039
|
...pageUrl ? { pageUrl } : {},
|
|
1038
|
-
...attachments.length > 0 ? { attachments: [...attachments] } : {}
|
|
1040
|
+
...attachments.length > 0 ? { attachments: [...attachments] } : {},
|
|
1041
|
+
...surface !== null ? { surface } : {}
|
|
1039
1042
|
};
|
|
1040
1043
|
const res = await client.mutate(
|
|
1041
1044
|
SEND_EXPLORE_MESSAGE_MUTATION,
|
|
1042
1045
|
{ input },
|
|
1043
1046
|
{ timeoutMs: SEND_REQUEST_TIMEOUT_MS }
|
|
1044
1047
|
);
|
|
1045
|
-
if (!mountedRef.current) return;
|
|
1048
|
+
if (!mountedRef.current) return null;
|
|
1046
1049
|
const first = res.errors?.[0];
|
|
1047
1050
|
const result = res.data?.sendExploreMessage;
|
|
1048
1051
|
if (first || !result) {
|
|
1049
1052
|
const code = typeof first?.extensions?.code === "string" ? first.extensions.code : void 0;
|
|
1050
1053
|
const classified = classifyExploreError(code, first?.message);
|
|
1051
1054
|
if (classified.kind === "captcha" && captchaAttempt === 0) {
|
|
1052
|
-
|
|
1053
|
-
return;
|
|
1055
|
+
return submit(text, optimisticId, 1, attachments, surface, replaces);
|
|
1054
1056
|
}
|
|
1055
1057
|
inFlightRef.current = false;
|
|
1056
1058
|
setError(classified);
|
|
1057
1059
|
setPhase(classified.kind === "rate-limit" ? "limited" : "error");
|
|
1058
|
-
return;
|
|
1060
|
+
return null;
|
|
1059
1061
|
}
|
|
1060
1062
|
rememberConversationToken(result.conversation.token);
|
|
1061
1063
|
turnCountRef.current = result.conversation.turnCount;
|
|
@@ -1067,6 +1069,15 @@ function useExploreChat(options = {}) {
|
|
|
1067
1069
|
const replaced = prev.some((m) => m.id === optimisticId) ? prev.map((m) => m.id === optimisticId ? userMessage : m) : [...prev, userMessage];
|
|
1068
1070
|
return [...replaced, assistantMessage];
|
|
1069
1071
|
});
|
|
1072
|
+
if (replaces !== null) {
|
|
1073
|
+
setResent({
|
|
1074
|
+
replacedMessageIds: [...replaces, optimisticId],
|
|
1075
|
+
sent: {
|
|
1076
|
+
userMessageId: userMessage.id,
|
|
1077
|
+
conversationToken: result.conversation.token
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1070
1081
|
if (assistantMessage.status === "COMPLETED" || assistantMessage.status === "FAILED") {
|
|
1071
1082
|
inFlightRef.current = false;
|
|
1072
1083
|
if (assistantMessage.status === "FAILED") {
|
|
@@ -1078,10 +1089,11 @@ function useExploreChat(options = {}) {
|
|
|
1078
1089
|
} else {
|
|
1079
1090
|
setPhase("ready");
|
|
1080
1091
|
}
|
|
1081
|
-
return;
|
|
1092
|
+
return userMessage.id;
|
|
1082
1093
|
}
|
|
1083
1094
|
setPhase("streaming");
|
|
1084
1095
|
startPolling(result.conversation.token, assistantMessage.id);
|
|
1096
|
+
return userMessage.id;
|
|
1085
1097
|
},
|
|
1086
1098
|
[
|
|
1087
1099
|
client,
|
|
@@ -1092,16 +1104,17 @@ function useExploreChat(options = {}) {
|
|
|
1092
1104
|
startPolling
|
|
1093
1105
|
]
|
|
1094
1106
|
);
|
|
1095
|
-
const
|
|
1096
|
-
async (message, attachments
|
|
1107
|
+
const sendQuestion = react.useCallback(
|
|
1108
|
+
async (message, attachments, options2, replaces) => {
|
|
1097
1109
|
const text = message.trim();
|
|
1098
|
-
|
|
1099
|
-
if (
|
|
1110
|
+
const surface = options2.surface ?? null;
|
|
1111
|
+
if (!text) return null;
|
|
1112
|
+
if (inFlightRef.current) return null;
|
|
1100
1113
|
const activeCatalog = catalogRef.current;
|
|
1101
1114
|
if (!activeCatalog.enabled) {
|
|
1102
1115
|
setError(classifyExploreError("EXPLORE_DISABLED"));
|
|
1103
1116
|
setPhase("disabled");
|
|
1104
|
-
return;
|
|
1117
|
+
return null;
|
|
1105
1118
|
}
|
|
1106
1119
|
if (text.length > activeCatalog.limits.maxMessageLength) {
|
|
1107
1120
|
setError({
|
|
@@ -1110,7 +1123,7 @@ function useExploreChat(options = {}) {
|
|
|
1110
1123
|
message: `That message is ${text.length} characters \u2014 the limit is ${activeCatalog.limits.maxMessageLength}.`,
|
|
1111
1124
|
retryable: false
|
|
1112
1125
|
});
|
|
1113
|
-
return;
|
|
1126
|
+
return null;
|
|
1114
1127
|
}
|
|
1115
1128
|
if (turnCountRef.current >= activeCatalog.limits.maxTurnsPerConversation) {
|
|
1116
1129
|
setError({
|
|
@@ -1119,11 +1132,12 @@ function useExploreChat(options = {}) {
|
|
|
1119
1132
|
message: `This conversation has reached its ${activeCatalog.limits.maxTurnsPerConversation}-message limit. Start a new chat to keep going.`,
|
|
1120
1133
|
retryable: false
|
|
1121
1134
|
});
|
|
1122
|
-
return;
|
|
1135
|
+
return null;
|
|
1123
1136
|
}
|
|
1124
1137
|
inFlightRef.current = true;
|
|
1125
1138
|
lastSentTextRef.current = text;
|
|
1126
1139
|
lastSentAttachmentsRef.current = attachments;
|
|
1140
|
+
lastSentSurfaceRef.current = surface;
|
|
1127
1141
|
setError(null);
|
|
1128
1142
|
setPhase("sending");
|
|
1129
1143
|
cancelPolling();
|
|
@@ -1142,21 +1156,47 @@ function useExploreChat(options = {}) {
|
|
|
1142
1156
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1143
1157
|
};
|
|
1144
1158
|
setMessages((prev) => [...prev, optimistic]);
|
|
1159
|
+
if (replaces !== null) {
|
|
1160
|
+
setResent({
|
|
1161
|
+
replacedMessageIds: replaces,
|
|
1162
|
+
sent: {
|
|
1163
|
+
userMessageId: optimistic.id,
|
|
1164
|
+
conversationToken: conversationTokenRef.current
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1145
1168
|
turnCountRef.current += 1;
|
|
1146
1169
|
setTurnCount(turnCountRef.current);
|
|
1147
1170
|
onSend?.(text);
|
|
1171
|
+
let userMessageId = optimistic.id;
|
|
1148
1172
|
try {
|
|
1149
|
-
await submit(
|
|
1173
|
+
userMessageId = await submit(
|
|
1174
|
+
text,
|
|
1175
|
+
optimistic.id,
|
|
1176
|
+
0,
|
|
1177
|
+
attachments,
|
|
1178
|
+
surface,
|
|
1179
|
+
replaces
|
|
1180
|
+
) ?? optimistic.id;
|
|
1150
1181
|
} catch (err) {
|
|
1151
|
-
if (!mountedRef.current) return;
|
|
1182
|
+
if (!mountedRef.current) return null;
|
|
1152
1183
|
console.error("[WebSDK] Explore send failed", err);
|
|
1153
1184
|
inFlightRef.current = false;
|
|
1154
1185
|
setError(classifyExploreError("NETWORK_ERROR"));
|
|
1155
1186
|
setPhase("error");
|
|
1156
1187
|
}
|
|
1188
|
+
if (!mountedRef.current) return null;
|
|
1189
|
+
return {
|
|
1190
|
+
userMessageId,
|
|
1191
|
+
conversationToken: conversationTokenRef.current
|
|
1192
|
+
};
|
|
1157
1193
|
},
|
|
1158
1194
|
[cancelPolling, onSend, submit]
|
|
1159
1195
|
);
|
|
1196
|
+
const send = react.useCallback(
|
|
1197
|
+
(message, attachments = [], options2 = {}) => sendQuestion(message, attachments, options2, null),
|
|
1198
|
+
[sendQuestion]
|
|
1199
|
+
);
|
|
1160
1200
|
const stop = react.useCallback(() => {
|
|
1161
1201
|
cancelPolling();
|
|
1162
1202
|
inFlightRef.current = false;
|
|
@@ -1185,6 +1225,13 @@ function useExploreChat(options = {}) {
|
|
|
1185
1225
|
if (!text) return;
|
|
1186
1226
|
cancelPolling();
|
|
1187
1227
|
inFlightRef.current = false;
|
|
1228
|
+
const shown = [...messagesRef.current];
|
|
1229
|
+
const lastShown = shown[shown.length - 1];
|
|
1230
|
+
if (lastShown?.role === "ASSISTANT" && lastShown.status === "FAILED") {
|
|
1231
|
+
shown.pop();
|
|
1232
|
+
}
|
|
1233
|
+
const priorShown = shown[shown.length - 1];
|
|
1234
|
+
const replaces = priorShown?.role === "USER" && priorShown.content === text ? [priorShown.id] : null;
|
|
1188
1235
|
setMessages((prev) => {
|
|
1189
1236
|
const next = [...prev];
|
|
1190
1237
|
const last = next[next.length - 1];
|
|
@@ -1195,8 +1242,14 @@ function useExploreChat(options = {}) {
|
|
|
1195
1242
|
});
|
|
1196
1243
|
turnCountRef.current = Math.max(0, turnCountRef.current - 1);
|
|
1197
1244
|
setTurnCount(turnCountRef.current);
|
|
1198
|
-
|
|
1199
|
-
|
|
1245
|
+
const surface = lastSentSurfaceRef.current;
|
|
1246
|
+
void sendQuestion(
|
|
1247
|
+
text,
|
|
1248
|
+
lastSentAttachmentsRef.current,
|
|
1249
|
+
surface === null ? {} : { surface },
|
|
1250
|
+
replaces
|
|
1251
|
+
);
|
|
1252
|
+
}, [cancelPolling, sendQuestion]);
|
|
1200
1253
|
const reset = react.useCallback(() => {
|
|
1201
1254
|
setArchive(
|
|
1202
1255
|
upsertArchive(storageKey, {
|
|
@@ -1210,9 +1263,11 @@ function useExploreChat(options = {}) {
|
|
|
1210
1263
|
rememberConversationToken(null);
|
|
1211
1264
|
lastSentTextRef.current = null;
|
|
1212
1265
|
lastSentAttachmentsRef.current = [];
|
|
1266
|
+
lastSentSurfaceRef.current = null;
|
|
1213
1267
|
turnCountRef.current = 0;
|
|
1214
1268
|
setTurnCount(0);
|
|
1215
1269
|
setMessages([]);
|
|
1270
|
+
setResent(null);
|
|
1216
1271
|
setError(null);
|
|
1217
1272
|
setRemainingToday(null);
|
|
1218
1273
|
setPhase(catalogRef.current.enabled ? "ready" : "disabled");
|
|
@@ -1415,6 +1470,7 @@ function useExploreChat(options = {}) {
|
|
|
1415
1470
|
send,
|
|
1416
1471
|
stop,
|
|
1417
1472
|
retry,
|
|
1473
|
+
resent,
|
|
1418
1474
|
reset,
|
|
1419
1475
|
canSend,
|
|
1420
1476
|
history: archive,
|
|
@@ -2037,5 +2093,5 @@ exports.useExploreChat = useExploreChat;
|
|
|
2037
2093
|
exports.useProgressiveReveal = useProgressiveReveal;
|
|
2038
2094
|
exports.useSpeechInput = useSpeechInput;
|
|
2039
2095
|
exports.useSpeechOutput = useSpeechOutput;
|
|
2040
|
-
//# sourceMappingURL=chunk-
|
|
2041
|
-
//# sourceMappingURL=chunk-
|
|
2096
|
+
//# sourceMappingURL=chunk-P5GMVPR6.js.map
|
|
2097
|
+
//# sourceMappingURL=chunk-P5GMVPR6.js.map
|