@burdenoff/website-sdk 2026.1027.1 → 2026.1029.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.
Files changed (60) hide show
  1. package/README.md +186 -1
  2. package/dist/avatar-live/index.d.mts +11 -0
  3. package/dist/avatar-live/index.d.ts +11 -0
  4. package/dist/avatar-live/index.js +120 -0
  5. package/dist/avatar-live/index.js.map +1 -0
  6. package/dist/avatar-live/index.mjs +118 -0
  7. package/dist/avatar-live/index.mjs.map +1 -0
  8. package/dist/avatar-live-view/index.d.mts +42 -0
  9. package/dist/avatar-live-view/index.d.ts +42 -0
  10. package/dist/avatar-live-view/index.js +1422 -0
  11. package/dist/avatar-live-view/index.js.map +1 -0
  12. package/dist/avatar-live-view/index.mjs +1420 -0
  13. package/dist/avatar-live-view/index.mjs.map +1 -0
  14. package/dist/comparison/index.d.mts +83 -0
  15. package/dist/comparison/index.d.ts +83 -0
  16. package/dist/comparison/index.js +116 -0
  17. package/dist/comparison/index.js.map +1 -0
  18. package/dist/comparison/index.mjs +109 -0
  19. package/dist/comparison/index.mjs.map +1 -0
  20. package/dist/components/explore.d.mts +2 -2
  21. package/dist/components/explore.d.ts +2 -2
  22. package/dist/components/explore.js +11 -4
  23. package/dist/components/explore.js.map +1 -1
  24. package/dist/components/explore.mjs +11 -4
  25. package/dist/components/explore.mjs.map +1 -1
  26. package/dist/components/partners.js.map +1 -1
  27. package/dist/components/partners.mjs.map +1 -1
  28. package/dist/content/index.d.mts +1 -1
  29. package/dist/content/index.d.ts +1 -1
  30. package/dist/content/index.js +32 -4
  31. package/dist/content/index.js.map +1 -1
  32. package/dist/content/index.mjs +32 -5
  33. package/dist/content/index.mjs.map +1 -1
  34. package/dist/{explore-BEgq1_0O.d.mts → explore-DUTNYUOm.d.mts} +1 -1
  35. package/dist/{explore-CHiP9u-4.d.ts → explore-DjQE7Xia.d.ts} +1 -1
  36. package/dist/explore-avatar-live-config-B171o1Yo.d.ts +283 -0
  37. package/dist/explore-avatar-live-config-BGsGwbcq.d.mts +283 -0
  38. package/dist/{explore-types-_9UJu6EA.d.mts → explore-types-Bb2Ih3d8.d.mts} +1 -1
  39. package/dist/{explore-types-_9UJu6EA.d.ts → explore-types-Bb2Ih3d8.d.ts} +1 -1
  40. package/dist/hooks/index.d.mts +3 -2
  41. package/dist/hooks/index.d.ts +3 -2
  42. package/dist/hooks/index.js +6 -1
  43. package/dist/hooks/index.js.map +1 -1
  44. package/dist/hooks/index.mjs +6 -1
  45. package/dist/hooks/index.mjs.map +1 -1
  46. package/dist/{index-CB0Uw1xD.d.ts → index-87BLB-Hj.d.ts} +26 -1
  47. package/dist/{index-Ctws7kGs.d.mts → index-Chw0pDXS.d.mts} +26 -1
  48. package/dist/index.d.mts +19 -255
  49. package/dist/index.d.ts +19 -255
  50. package/dist/index.js +358 -40
  51. package/dist/index.js.map +1 -1
  52. package/dist/index.mjs +352 -41
  53. package/dist/index.mjs.map +1 -1
  54. package/dist/types-CndbmBK-.d.mts +68 -0
  55. package/dist/types-CndbmBK-.d.ts +68 -0
  56. package/dist/{use-progressive-reveal-C0hkdu_i.d.ts → use-explore-chat-DunjG-3p.d.ts} +10 -36
  57. package/dist/{use-progressive-reveal-BFPWbEXy.d.mts → use-explore-chat-zRuoAin6.d.mts} +10 -36
  58. package/dist/use-progressive-reveal-ppicEgzZ.d.mts +35 -0
  59. package/dist/use-progressive-reveal-ppicEgzZ.d.ts +35 -0
  60. package/package.json +20 -3
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/comparison/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @burdenoff/website-sdk - Comparison page sources and freshness (BOFF-9971)\n *\n * Every product website with a /vs (competitor comparison) page makes claims\n * about other vendors. Those claims age fast: a vendor ships the feature we\n * said it lacked, or changes its prices. This module gives every site the same\n * contract and the same check:\n *\n * - each claim carries at least one source `{ url, verifiedAt }` (enforced by\n * the `Sourced` type at compile time), and\n * - `checkComparisonFreshness` fails when a claim has no source, a source is\n * not an http(s) URL, a date is malformed or in the future, or the newest\n * verification is older than `maxAgeDays` (180 by default).\n *\n * Sites run the check in their PR Check workflow, so a stale or unsourced\n * comparison blocks the next change until someone re-reads the vendor's page.\n *\n * @example\n * ```ts\n * import { checkComparisonFreshness, formatFreshnessReport } from '@burdenoff/website-sdk/comparison'\n *\n * const issues = checkComparisonFreshness(collectClaims(COMPETITORS))\n * if (issues.length) {\n * console.error(formatFreshnessReport(issues))\n * process.exit(1)\n * }\n * ```\n */\n\n/** Where a comparison claim was checked, and when (ISO date, YYYY-MM-DD). */\nexport interface ComparisonSource {\n /** The vendor page (or docs page) the claim was checked against. */\n url: string;\n /** The day a person last read that page and confirmed the claim. */\n verifiedAt: string;\n /** Optional short label shown with the link, e.g. \"Pricing\". */\n label?: string;\n}\n\n/** A non-empty list: a sourced claim always has at least one source. */\nexport type NonEmptySources = readonly [\n ComparisonSource,\n ...ComparisonSource[],\n];\n\n/** Mix-in for any comparison claim (table row, profile, pricing row). */\nexport interface Sourced {\n sources: NonEmptySources;\n}\n\n/** One claim to check, located by a human-readable path for the report. */\nexport interface ComparisonClaim {\n /** Where the claim lives, e.g. \"zapier.featureRows[Workflow versioning]\". */\n path: string;\n /** Its sources; missing or empty fails the check. */\n sources?: readonly ComparisonSource[] | null;\n}\n\nexport type FreshnessProblem =\n \"missing-source\" | \"invalid-url\" | \"invalid-date\" | \"future-date\" | \"stale\";\n\nexport interface FreshnessIssue {\n path: string;\n problem: FreshnessProblem;\n detail: string;\n}\n\nexport interface FreshnessOptions {\n /** Oldest acceptable verification, in days. Default 180. */\n maxAgeDays?: number;\n /** The clock to check against. Default: now. */\n now?: Date;\n}\n\n/** Default staleness threshold for comparison claims. */\nexport const DEFAULT_COMPARISON_MAX_AGE_DAYS = 180;\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Parse a strict YYYY-MM-DD date as UTC midnight, or null when invalid. */\nexport function parseVerifiedAt(value: string): Date | null {\n if (typeof value !== \"string\" || !ISO_DATE.test(value)) return null;\n const date = new Date(`${value}T00:00:00.000Z`);\n if (Number.isNaN(date.getTime())) return null;\n // Reject rollovers such as 2026-02-31 → 2026-03-03.\n return date.toISOString().slice(0, 10) === value ? date : null;\n}\n\nfunction isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" || url.protocol === \"http:\";\n } catch {\n return false;\n }\n}\n\n/**\n * Check every claim and return the problems found (empty when all is well).\n * A claim passes on its newest valid source, so adding a fresh source to an\n * old claim is enough to clear a \"stale\" issue.\n */\nexport function checkComparisonFreshness(\n claims: readonly ComparisonClaim[],\n options: FreshnessOptions = {},\n): FreshnessIssue[] {\n const maxAgeDays = options.maxAgeDays ?? DEFAULT_COMPARISON_MAX_AGE_DAYS;\n const now = options.now ?? new Date();\n const issues: FreshnessIssue[] = [];\n\n for (const claim of claims) {\n const sources = claim.sources ?? [];\n if (sources.length === 0) {\n issues.push({\n path: claim.path,\n problem: \"missing-source\",\n detail:\n \"no source: add { url, verifiedAt } for the vendor page this claim was checked against\",\n });\n continue;\n }\n\n let newest: Date | null = null;\n for (const source of sources) {\n if (!isHttpUrl(source.url)) {\n issues.push({\n path: claim.path,\n problem: \"invalid-url\",\n detail: `source URL is not an http(s) URL: ${JSON.stringify(source.url)}`,\n });\n }\n const verified = parseVerifiedAt(source.verifiedAt);\n if (!verified) {\n issues.push({\n path: claim.path,\n problem: \"invalid-date\",\n detail: `verifiedAt must be YYYY-MM-DD, got ${JSON.stringify(source.verifiedAt)}`,\n });\n continue;\n }\n if (verified.getTime() > now.getTime() + DAY_MS) {\n issues.push({\n path: claim.path,\n problem: \"future-date\",\n detail: `verifiedAt ${source.verifiedAt} is in the future`,\n });\n continue;\n }\n if (!newest || verified.getTime() > newest.getTime()) newest = verified;\n }\n\n if (newest) {\n const ageDays = Math.floor((now.getTime() - newest.getTime()) / DAY_MS);\n if (ageDays > maxAgeDays) {\n issues.push({\n path: claim.path,\n problem: \"stale\",\n detail: `last verified ${newest.toISOString().slice(0, 10)} (${ageDays} days ago, limit ${maxAgeDays}): re-read the vendor page and update verifiedAt or the claim`,\n });\n }\n }\n }\n\n return issues;\n}\n\n/** Newest valid verification date across all claims, or null. */\nexport function latestVerification(\n claims: readonly ComparisonClaim[],\n): string | null {\n let latest: string | null = null;\n for (const claim of claims) {\n for (const source of claim.sources ?? []) {\n if (\n parseVerifiedAt(source.verifiedAt) &&\n (!latest || source.verifiedAt > latest)\n ) {\n latest = source.verifiedAt;\n }\n }\n }\n return latest;\n}\n\n/** Oldest valid verification date across all claims, or null. */\nexport function oldestVerification(\n claims: readonly ComparisonClaim[],\n): string | null {\n let oldest: string | null = null;\n for (const claim of claims) {\n for (const source of claim.sources ?? []) {\n if (\n parseVerifiedAt(source.verifiedAt) &&\n (!oldest || source.verifiedAt < oldest)\n ) {\n oldest = source.verifiedAt;\n }\n }\n }\n return oldest;\n}\n\n/** A plain-text report for CI logs, one line per issue. */\nexport function formatFreshnessReport(\n issues: readonly FreshnessIssue[],\n): string {\n if (issues.length === 0)\n return \"Comparison sources: all claims sourced and fresh.\";\n const lines = issues.map((i) => ` - [${i.problem}] ${i.path}: ${i.detail}`);\n return [\n `Comparison sources: ${issues.length} problem(s) found.`,\n ...lines,\n \"Re-check each claim against the vendor's current page, then update the claim and its verifiedAt.\",\n ].join(\"\\n\");\n}\n"]}
@@ -0,0 +1,109 @@
1
+ // src/comparison/index.ts
2
+ var DEFAULT_COMPARISON_MAX_AGE_DAYS = 180;
3
+ var DAY_MS = 24 * 60 * 60 * 1e3;
4
+ var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
5
+ function parseVerifiedAt(value) {
6
+ if (typeof value !== "string" || !ISO_DATE.test(value)) return null;
7
+ const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
8
+ if (Number.isNaN(date.getTime())) return null;
9
+ return date.toISOString().slice(0, 10) === value ? date : null;
10
+ }
11
+ function isHttpUrl(value) {
12
+ try {
13
+ const url = new URL(value);
14
+ return url.protocol === "https:" || url.protocol === "http:";
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+ function checkComparisonFreshness(claims, options = {}) {
20
+ const maxAgeDays = options.maxAgeDays ?? DEFAULT_COMPARISON_MAX_AGE_DAYS;
21
+ const now = options.now ?? /* @__PURE__ */ new Date();
22
+ const issues = [];
23
+ for (const claim of claims) {
24
+ const sources = claim.sources ?? [];
25
+ if (sources.length === 0) {
26
+ issues.push({
27
+ path: claim.path,
28
+ problem: "missing-source",
29
+ detail: "no source: add { url, verifiedAt } for the vendor page this claim was checked against"
30
+ });
31
+ continue;
32
+ }
33
+ let newest = null;
34
+ for (const source of sources) {
35
+ if (!isHttpUrl(source.url)) {
36
+ issues.push({
37
+ path: claim.path,
38
+ problem: "invalid-url",
39
+ detail: `source URL is not an http(s) URL: ${JSON.stringify(source.url)}`
40
+ });
41
+ }
42
+ const verified = parseVerifiedAt(source.verifiedAt);
43
+ if (!verified) {
44
+ issues.push({
45
+ path: claim.path,
46
+ problem: "invalid-date",
47
+ detail: `verifiedAt must be YYYY-MM-DD, got ${JSON.stringify(source.verifiedAt)}`
48
+ });
49
+ continue;
50
+ }
51
+ if (verified.getTime() > now.getTime() + DAY_MS) {
52
+ issues.push({
53
+ path: claim.path,
54
+ problem: "future-date",
55
+ detail: `verifiedAt ${source.verifiedAt} is in the future`
56
+ });
57
+ continue;
58
+ }
59
+ if (!newest || verified.getTime() > newest.getTime()) newest = verified;
60
+ }
61
+ if (newest) {
62
+ const ageDays = Math.floor((now.getTime() - newest.getTime()) / DAY_MS);
63
+ if (ageDays > maxAgeDays) {
64
+ issues.push({
65
+ path: claim.path,
66
+ problem: "stale",
67
+ detail: `last verified ${newest.toISOString().slice(0, 10)} (${ageDays} days ago, limit ${maxAgeDays}): re-read the vendor page and update verifiedAt or the claim`
68
+ });
69
+ }
70
+ }
71
+ }
72
+ return issues;
73
+ }
74
+ function latestVerification(claims) {
75
+ let latest = null;
76
+ for (const claim of claims) {
77
+ for (const source of claim.sources ?? []) {
78
+ if (parseVerifiedAt(source.verifiedAt) && (!latest || source.verifiedAt > latest)) {
79
+ latest = source.verifiedAt;
80
+ }
81
+ }
82
+ }
83
+ return latest;
84
+ }
85
+ function oldestVerification(claims) {
86
+ let oldest = null;
87
+ for (const claim of claims) {
88
+ for (const source of claim.sources ?? []) {
89
+ if (parseVerifiedAt(source.verifiedAt) && (!oldest || source.verifiedAt < oldest)) {
90
+ oldest = source.verifiedAt;
91
+ }
92
+ }
93
+ }
94
+ return oldest;
95
+ }
96
+ function formatFreshnessReport(issues) {
97
+ if (issues.length === 0)
98
+ return "Comparison sources: all claims sourced and fresh.";
99
+ const lines = issues.map((i) => ` - [${i.problem}] ${i.path}: ${i.detail}`);
100
+ return [
101
+ `Comparison sources: ${issues.length} problem(s) found.`,
102
+ ...lines,
103
+ "Re-check each claim against the vendor's current page, then update the claim and its verifiedAt."
104
+ ].join("\n");
105
+ }
106
+
107
+ export { DEFAULT_COMPARISON_MAX_AGE_DAYS, checkComparisonFreshness, formatFreshnessReport, latestVerification, oldestVerification, parseVerifiedAt };
108
+ //# sourceMappingURL=index.mjs.map
109
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/comparison/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @burdenoff/website-sdk - Comparison page sources and freshness (BOFF-9971)\n *\n * Every product website with a /vs (competitor comparison) page makes claims\n * about other vendors. Those claims age fast: a vendor ships the feature we\n * said it lacked, or changes its prices. This module gives every site the same\n * contract and the same check:\n *\n * - each claim carries at least one source `{ url, verifiedAt }` (enforced by\n * the `Sourced` type at compile time), and\n * - `checkComparisonFreshness` fails when a claim has no source, a source is\n * not an http(s) URL, a date is malformed or in the future, or the newest\n * verification is older than `maxAgeDays` (180 by default).\n *\n * Sites run the check in their PR Check workflow, so a stale or unsourced\n * comparison blocks the next change until someone re-reads the vendor's page.\n *\n * @example\n * ```ts\n * import { checkComparisonFreshness, formatFreshnessReport } from '@burdenoff/website-sdk/comparison'\n *\n * const issues = checkComparisonFreshness(collectClaims(COMPETITORS))\n * if (issues.length) {\n * console.error(formatFreshnessReport(issues))\n * process.exit(1)\n * }\n * ```\n */\n\n/** Where a comparison claim was checked, and when (ISO date, YYYY-MM-DD). */\nexport interface ComparisonSource {\n /** The vendor page (or docs page) the claim was checked against. */\n url: string;\n /** The day a person last read that page and confirmed the claim. */\n verifiedAt: string;\n /** Optional short label shown with the link, e.g. \"Pricing\". */\n label?: string;\n}\n\n/** A non-empty list: a sourced claim always has at least one source. */\nexport type NonEmptySources = readonly [\n ComparisonSource,\n ...ComparisonSource[],\n];\n\n/** Mix-in for any comparison claim (table row, profile, pricing row). */\nexport interface Sourced {\n sources: NonEmptySources;\n}\n\n/** One claim to check, located by a human-readable path for the report. */\nexport interface ComparisonClaim {\n /** Where the claim lives, e.g. \"zapier.featureRows[Workflow versioning]\". */\n path: string;\n /** Its sources; missing or empty fails the check. */\n sources?: readonly ComparisonSource[] | null;\n}\n\nexport type FreshnessProblem =\n \"missing-source\" | \"invalid-url\" | \"invalid-date\" | \"future-date\" | \"stale\";\n\nexport interface FreshnessIssue {\n path: string;\n problem: FreshnessProblem;\n detail: string;\n}\n\nexport interface FreshnessOptions {\n /** Oldest acceptable verification, in days. Default 180. */\n maxAgeDays?: number;\n /** The clock to check against. Default: now. */\n now?: Date;\n}\n\n/** Default staleness threshold for comparison claims. */\nexport const DEFAULT_COMPARISON_MAX_AGE_DAYS = 180;\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Parse a strict YYYY-MM-DD date as UTC midnight, or null when invalid. */\nexport function parseVerifiedAt(value: string): Date | null {\n if (typeof value !== \"string\" || !ISO_DATE.test(value)) return null;\n const date = new Date(`${value}T00:00:00.000Z`);\n if (Number.isNaN(date.getTime())) return null;\n // Reject rollovers such as 2026-02-31 → 2026-03-03.\n return date.toISOString().slice(0, 10) === value ? date : null;\n}\n\nfunction isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === \"https:\" || url.protocol === \"http:\";\n } catch {\n return false;\n }\n}\n\n/**\n * Check every claim and return the problems found (empty when all is well).\n * A claim passes on its newest valid source, so adding a fresh source to an\n * old claim is enough to clear a \"stale\" issue.\n */\nexport function checkComparisonFreshness(\n claims: readonly ComparisonClaim[],\n options: FreshnessOptions = {},\n): FreshnessIssue[] {\n const maxAgeDays = options.maxAgeDays ?? DEFAULT_COMPARISON_MAX_AGE_DAYS;\n const now = options.now ?? new Date();\n const issues: FreshnessIssue[] = [];\n\n for (const claim of claims) {\n const sources = claim.sources ?? [];\n if (sources.length === 0) {\n issues.push({\n path: claim.path,\n problem: \"missing-source\",\n detail:\n \"no source: add { url, verifiedAt } for the vendor page this claim was checked against\",\n });\n continue;\n }\n\n let newest: Date | null = null;\n for (const source of sources) {\n if (!isHttpUrl(source.url)) {\n issues.push({\n path: claim.path,\n problem: \"invalid-url\",\n detail: `source URL is not an http(s) URL: ${JSON.stringify(source.url)}`,\n });\n }\n const verified = parseVerifiedAt(source.verifiedAt);\n if (!verified) {\n issues.push({\n path: claim.path,\n problem: \"invalid-date\",\n detail: `verifiedAt must be YYYY-MM-DD, got ${JSON.stringify(source.verifiedAt)}`,\n });\n continue;\n }\n if (verified.getTime() > now.getTime() + DAY_MS) {\n issues.push({\n path: claim.path,\n problem: \"future-date\",\n detail: `verifiedAt ${source.verifiedAt} is in the future`,\n });\n continue;\n }\n if (!newest || verified.getTime() > newest.getTime()) newest = verified;\n }\n\n if (newest) {\n const ageDays = Math.floor((now.getTime() - newest.getTime()) / DAY_MS);\n if (ageDays > maxAgeDays) {\n issues.push({\n path: claim.path,\n problem: \"stale\",\n detail: `last verified ${newest.toISOString().slice(0, 10)} (${ageDays} days ago, limit ${maxAgeDays}): re-read the vendor page and update verifiedAt or the claim`,\n });\n }\n }\n }\n\n return issues;\n}\n\n/** Newest valid verification date across all claims, or null. */\nexport function latestVerification(\n claims: readonly ComparisonClaim[],\n): string | null {\n let latest: string | null = null;\n for (const claim of claims) {\n for (const source of claim.sources ?? []) {\n if (\n parseVerifiedAt(source.verifiedAt) &&\n (!latest || source.verifiedAt > latest)\n ) {\n latest = source.verifiedAt;\n }\n }\n }\n return latest;\n}\n\n/** Oldest valid verification date across all claims, or null. */\nexport function oldestVerification(\n claims: readonly ComparisonClaim[],\n): string | null {\n let oldest: string | null = null;\n for (const claim of claims) {\n for (const source of claim.sources ?? []) {\n if (\n parseVerifiedAt(source.verifiedAt) &&\n (!oldest || source.verifiedAt < oldest)\n ) {\n oldest = source.verifiedAt;\n }\n }\n }\n return oldest;\n}\n\n/** A plain-text report for CI logs, one line per issue. */\nexport function formatFreshnessReport(\n issues: readonly FreshnessIssue[],\n): string {\n if (issues.length === 0)\n return \"Comparison sources: all claims sourced and fresh.\";\n const lines = issues.map((i) => ` - [${i.problem}] ${i.path}: ${i.detail}`);\n return [\n `Comparison sources: ${issues.length} problem(s) found.`,\n ...lines,\n \"Re-check each claim against the vendor's current page, then update the claim and its verifiedAt.\",\n ].join(\"\\n\");\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-BEgq1_0O.mjs';
3
- import '../explore-types-_9UJu6EA.mjs';
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-DUTNYUOm.mjs';
3
+ import '../explore-types-Bb2Ih3d8.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-CHiP9u-4.js';
3
- import '../explore-types-_9UJu6EA.js';
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-DjQE7Xia.js';
3
+ import '../explore-types-Bb2Ih3d8.js';
4
4
  import 'react-markdown';
@@ -42,6 +42,11 @@ var __defProp = Object.defineProperty;
42
42
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
43
43
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
44
44
 
45
+ // src/components/explore-fill.ts
46
+ function fill(template, values) {
47
+ return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
48
+ }
49
+
45
50
  // src/components/explore-i18n.ts
46
51
  var EXPLORE_LOCALES = ["en", "zh", "hi", "es", "ar", "ta"];
47
52
  var RTL_LOCALES = /* @__PURE__ */ new Set(["ar"]);
@@ -829,9 +834,6 @@ function resolveExploreLocale(explicit) {
829
834
  function exploreStrings(locale) {
830
835
  return STRINGS[locale] ?? EN;
831
836
  }
832
- function fill(template, values) {
833
- return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
834
- }
835
837
  function fullscreenSupport() {
836
838
  if (typeof document === "undefined") return null;
837
839
  if (document.fullscreenEnabled && typeof document.documentElement.requestFullscreen === "function") {
@@ -3505,6 +3507,10 @@ function useExploreChat(options = {}) {
3505
3507
  [client, productId]
3506
3508
  );
3507
3509
  const canEscalate = hasConversation && !isSharedView;
3510
+ const getConversationToken = React.useCallback(
3511
+ () => conversationTokenRef.current,
3512
+ []
3513
+ );
3508
3514
  return {
3509
3515
  phase,
3510
3516
  catalog,
@@ -3529,7 +3535,8 @@ function useExploreChat(options = {}) {
3529
3535
  escalate,
3530
3536
  canEscalate,
3531
3537
  createShareLink,
3532
- isSharedView
3538
+ isSharedView,
3539
+ getConversationToken
3533
3540
  };
3534
3541
  }
3535
3542
  var optimisticCounter = 0;