@burdenoff/website-sdk 2026.824.2 → 2026.828.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 (124) hide show
  1. package/README.md +88 -0
  2. package/dist/client/index.d.mts +2 -3
  3. package/dist/client/index.d.ts +2 -3
  4. package/dist/client/index.js +13 -7
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/client/index.mjs +13 -7
  7. package/dist/client/index.mjs.map +1 -1
  8. package/dist/components/contact.d.mts +2 -2
  9. package/dist/components/contact.d.ts +2 -2
  10. package/dist/components/contact.js +13 -7
  11. package/dist/components/contact.js.map +1 -1
  12. package/dist/components/contact.mjs +13 -7
  13. package/dist/components/contact.mjs.map +1 -1
  14. package/dist/components/electron-download.d.mts +2 -2
  15. package/dist/components/electron-download.d.ts +2 -2
  16. package/dist/components/explore-cta.d.mts +26 -0
  17. package/dist/components/explore-cta.d.ts +26 -0
  18. package/dist/components/explore-cta.js +95 -0
  19. package/dist/components/explore-cta.js.map +1 -0
  20. package/dist/components/explore-cta.mjs +93 -0
  21. package/dist/components/explore-cta.mjs.map +1 -0
  22. package/dist/components/explore.d.mts +43 -0
  23. package/dist/components/explore.d.ts +43 -0
  24. package/dist/components/explore.js +2219 -0
  25. package/dist/components/explore.js.map +1 -0
  26. package/dist/components/explore.mjs +2193 -0
  27. package/dist/components/explore.mjs.map +1 -0
  28. package/dist/components/launch-countdown.d.mts +2 -2
  29. package/dist/components/launch-countdown.d.ts +2 -2
  30. package/dist/components/newsletter.d.mts +3 -3
  31. package/dist/components/newsletter.d.ts +3 -3
  32. package/dist/components/newsletter.js +13 -7
  33. package/dist/components/newsletter.js.map +1 -1
  34. package/dist/components/newsletter.mjs +13 -7
  35. package/dist/components/newsletter.mjs.map +1 -1
  36. package/dist/components/partners.d.mts +2 -2
  37. package/dist/components/partners.d.ts +2 -2
  38. package/dist/components/partners.js +13 -7
  39. package/dist/components/partners.js.map +1 -1
  40. package/dist/components/partners.mjs +13 -7
  41. package/dist/components/partners.mjs.map +1 -1
  42. package/dist/components/pricing.d.mts +3 -3
  43. package/dist/components/pricing.d.ts +3 -3
  44. package/dist/components/pricing.js +13 -7
  45. package/dist/components/pricing.js.map +1 -1
  46. package/dist/components/pricing.mjs +13 -7
  47. package/dist/components/pricing.mjs.map +1 -1
  48. package/dist/components/problems-solved.d.mts +2 -2
  49. package/dist/components/problems-solved.d.ts +2 -2
  50. package/dist/components/resource-links.d.mts +2 -2
  51. package/dist/components/resource-links.d.ts +2 -2
  52. package/dist/components/resource-links.js +13 -7
  53. package/dist/components/resource-links.js.map +1 -1
  54. package/dist/components/resource-links.mjs +13 -7
  55. package/dist/components/resource-links.mjs.map +1 -1
  56. package/dist/components/social-links.d.mts +2 -2
  57. package/dist/components/social-links.d.ts +2 -2
  58. package/dist/components/social-links.js +13 -7
  59. package/dist/components/social-links.js.map +1 -1
  60. package/dist/components/social-links.mjs +13 -7
  61. package/dist/components/social-links.mjs.map +1 -1
  62. package/dist/components/unsubscribe.d.mts +2 -2
  63. package/dist/components/unsubscribe.d.ts +2 -2
  64. package/dist/components/unsubscribe.js +13 -7
  65. package/dist/components/unsubscribe.js.map +1 -1
  66. package/dist/components/unsubscribe.mjs +13 -7
  67. package/dist/components/unsubscribe.mjs.map +1 -1
  68. package/dist/components/waitlist.d.mts +3 -3
  69. package/dist/components/waitlist.d.ts +3 -3
  70. package/dist/components/waitlist.js +13 -7
  71. package/dist/components/waitlist.js.map +1 -1
  72. package/dist/components/waitlist.mjs +13 -7
  73. package/dist/components/waitlist.mjs.map +1 -1
  74. package/dist/components/website-switcher.d.mts +2 -2
  75. package/dist/components/website-switcher.d.ts +2 -2
  76. package/dist/content/index.d.mts +2 -3
  77. package/dist/content/index.d.ts +2 -3
  78. package/dist/content/index.js +13 -7
  79. package/dist/content/index.js.map +1 -1
  80. package/dist/content/index.mjs +13 -7
  81. package/dist/content/index.mjs.map +1 -1
  82. package/dist/{graphql-client-Cv6TO6hn.d.mts → graphql-client-BXn6RYV0.d.mts} +31 -4
  83. package/dist/{graphql-client-Cv6TO6hn.d.ts → graphql-client-BXn6RYV0.d.ts} +31 -4
  84. package/dist/hooks/index.d.mts +1 -0
  85. package/dist/hooks/index.d.ts +1 -0
  86. package/dist/hooks/index.js +1145 -0
  87. package/dist/hooks/index.js.map +1 -0
  88. package/dist/hooks/index.mjs +1140 -0
  89. package/dist/hooks/index.mjs.map +1 -0
  90. package/dist/{index-CFsUFxYo.d.ts → index-BXg7cQLm.d.ts} +11 -11
  91. package/dist/index-CUJTTlYx.d.mts +244 -0
  92. package/dist/index-CUJTTlYx.d.ts +244 -0
  93. package/dist/{index-CuMRiKGB.d.mts → index-DYxQiEm5.d.mts} +11 -11
  94. package/dist/index.d.mts +17 -15
  95. package/dist/index.d.ts +17 -15
  96. package/dist/index.js +1987 -426
  97. package/dist/index.js.map +1 -1
  98. package/dist/index.mjs +1977 -428
  99. package/dist/index.mjs.map +1 -1
  100. package/dist/product/index.d.mts +2 -2
  101. package/dist/product/index.d.ts +2 -2
  102. package/dist/product/index.js +13 -7
  103. package/dist/product/index.js.map +1 -1
  104. package/dist/product/index.mjs +13 -7
  105. package/dist/product/index.mjs.map +1 -1
  106. package/dist/{product-card-DL9O3Ez9.d.mts → product-card-CGxETsFa.d.mts} +7 -7
  107. package/dist/{product-card-DL9O3Ez9.d.ts → product-card-CGxETsFa.d.ts} +7 -7
  108. package/dist/{provider-D-Do5PmD.d.ts → provider-CUfI8meE.d.mts} +3 -3
  109. package/dist/{provider-BhsbxVPo.d.mts → provider-D_uUM7_e.d.ts} +3 -3
  110. package/dist/seo/index.d.mts +2 -2
  111. package/dist/seo/index.d.ts +2 -2
  112. package/dist/store/index.d.mts +3 -3
  113. package/dist/store/index.d.ts +3 -3
  114. package/dist/store/index.js +13 -7
  115. package/dist/store/index.js.map +1 -1
  116. package/dist/store/index.mjs +13 -7
  117. package/dist/store/index.mjs.map +1 -1
  118. package/dist/utils/index.d.mts +49 -1
  119. package/dist/utils/index.d.ts +49 -1
  120. package/dist/utils/index.js +70 -0
  121. package/dist/utils/index.js.map +1 -1
  122. package/dist/utils/index.mjs +68 -1
  123. package/dist/utils/index.mjs.map +1 -1
  124. package/package.json +40 -30
package/dist/index.js CHANGED
@@ -105,9 +105,15 @@ var _WebSDKClient = class _WebSDKClient {
105
105
  return this.config;
106
106
  }
107
107
  /**
108
- * Execute a GraphQL query (with deduplication and caching)
108
+ * Execute a GraphQL query (with deduplication and caching).
109
+ *
110
+ * Pass `{ cache: false }` to bypass the cache entirely — required for poll
111
+ * loops, where every tick must reach the network.
109
112
  */
110
- async query(query, variables) {
113
+ async query(query, variables, options) {
114
+ if (options?.cache === false) {
115
+ return this.request(query, variables, true, options.timeoutMs);
116
+ }
111
117
  const cacheKey = JSON.stringify({ query, variables });
112
118
  const cached = this.queryCache.get(cacheKey);
113
119
  if (cached) {
@@ -120,7 +126,7 @@ var _WebSDKClient = class _WebSDKClient {
120
126
  if (existing) {
121
127
  return existing;
122
128
  }
123
- const promise = this.request(query, variables).then((result) => {
129
+ const promise = this.request(query, variables, true, options?.timeoutMs).then((result) => {
124
130
  if (result.data && !result.errors?.length) {
125
131
  if (this.queryCache.size >= _WebSDKClient.MAX_CACHE_ENTRIES) {
126
132
  const oldestEntry = this.queryCache.keys().next().value;
@@ -140,8 +146,8 @@ var _WebSDKClient = class _WebSDKClient {
140
146
  /**
141
147
  * Execute a GraphQL mutation
142
148
  */
143
- async mutate(mutation, variables) {
144
- return this.request(mutation, variables, false);
149
+ async mutate(mutation, variables, options) {
150
+ return this.request(mutation, variables, false, options?.timeoutMs);
145
151
  }
146
152
  /** HTTP statuses worth another attempt — transient server/edge conditions. */
147
153
  static isRetriableStatus(status) {
@@ -212,8 +218,8 @@ var _WebSDKClient = class _WebSDKClient {
212
218
  clearTimeout(timer);
213
219
  }
214
220
  }
215
- async request(query, variables, retry = true) {
216
- const timeoutMs = this.config.timeoutMs ?? _WebSDKClient.DEFAULT_TIMEOUT_MS;
221
+ async request(query, variables, retry = true, timeoutOverrideMs) {
222
+ const timeoutMs = timeoutOverrideMs ?? this.config.timeoutMs ?? _WebSDKClient.DEFAULT_TIMEOUT_MS;
217
223
  const maxRetries = retry ? this.config.maxRetries ?? _WebSDKClient.DEFAULT_MAX_RETRIES : 0;
218
224
  let last;
219
225
  for (let i = 0; i <= maxRetries; i++) {
@@ -4738,228 +4744,6 @@ function ElectronDownloadLink({
4738
4744
  }
4739
4745
  );
4740
4746
  }
4741
- function RybbitAnalytics({
4742
- siteId,
4743
- scriptUrl = "https://analytics.burdenoff.com/api/script.js",
4744
- enabled = true
4745
- }) {
4746
- React.useEffect(() => {
4747
- if (!enabled || !siteId) return;
4748
- const existingScript = document.querySelector(
4749
- `script[data-site-id="${siteId}"]`
4750
- );
4751
- if (existingScript) return;
4752
- const script = document.createElement("script");
4753
- script.src = scriptUrl;
4754
- script.defer = true;
4755
- script.crossOrigin = "anonymous";
4756
- script.setAttribute("data-site-id", siteId);
4757
- document.head.appendChild(script);
4758
- return () => {
4759
- const scriptToRemove = document.querySelector(
4760
- `script[data-site-id="${siteId}"]`
4761
- );
4762
- if (scriptToRemove) {
4763
- document.head.removeChild(scriptToRemove);
4764
- }
4765
- };
4766
- }, [siteId, scriptUrl, enabled]);
4767
- return null;
4768
- }
4769
- var PUBLIC_JOB_POSTING_FIELDS = (
4770
- /* GraphQL */
4771
- `
4772
- id
4773
- slug
4774
- title
4775
- department
4776
- location
4777
- employmentType
4778
- openings
4779
- description
4780
- overview
4781
- responsibilities
4782
- learnings
4783
- qualifications
4784
- rounds {
4785
- roundNumber
4786
- name
4787
- roundType
4788
- isRequired
4789
- description
4790
- }
4791
- `
4792
- );
4793
- var PUBLIC_JOB_POSTINGS_QUERY = (
4794
- /* GraphQL */
4795
- `
4796
- query PublicJobPostings($productId: ID, $limit: Int, $offset: Int) {
4797
- publicJobPostings(productId: $productId, limit: $limit, offset: $offset) {
4798
- ${PUBLIC_JOB_POSTING_FIELDS}
4799
- }
4800
- }
4801
- `
4802
- );
4803
- var PUBLIC_JOB_POSTING_QUERY = (
4804
- /* GraphQL */
4805
- `
4806
- query PublicJobPosting($slug: String!, $productId: ID) {
4807
- publicJobPosting(slug: $slug, productId: $productId) {
4808
- ${PUBLIC_JOB_POSTING_FIELDS}
4809
- }
4810
- }
4811
- `
4812
- );
4813
- var PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY = (
4814
- /* GraphQL */
4815
- `
4816
- query PublicJobPostingsForSubmission(
4817
- $productId: ID
4818
- $limit: Int
4819
- $offset: Int
4820
- ) {
4821
- publicJobPostingsForSubmission(
4822
- productId: $productId
4823
- limit: $limit
4824
- offset: $offset
4825
- ) {
4826
- id
4827
- title
4828
- }
4829
- }
4830
- `
4831
- );
4832
- async function fetchPublicJobPostings(client, options) {
4833
- const result = await client.query(PUBLIC_JOB_POSTINGS_QUERY, {
4834
- productId: options?.productId,
4835
- limit: options?.limit ?? 50,
4836
- offset: options?.offset ?? 0
4837
- });
4838
- if (result.errors?.length) {
4839
- throw new Error(result.errors[0]?.message ?? "Unknown error");
4840
- }
4841
- return result.data?.publicJobPostings ?? [];
4842
- }
4843
- async function fetchPublicJobPosting(client, slug, options) {
4844
- const result = await client.query(PUBLIC_JOB_POSTING_QUERY, {
4845
- slug,
4846
- productId: options?.productId
4847
- });
4848
- if (result.errors?.length) {
4849
- throw new Error(result.errors[0]?.message ?? "Unknown error");
4850
- }
4851
- return result.data?.publicJobPosting ?? null;
4852
- }
4853
- async function fetchPublicJobPostingsForSubmission(client, options) {
4854
- const result = await client.query(PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY, {
4855
- productId: options?.productId,
4856
- limit: options?.limit ?? 50,
4857
- offset: options?.offset ?? 0
4858
- });
4859
- if (result.errors?.length) {
4860
- throw new Error(result.errors[0]?.message ?? "Unknown error");
4861
- }
4862
- return result.data?.publicJobPostingsForSubmission ?? [];
4863
- }
4864
- function usePublicJobPostings(options) {
4865
- const client = useWebSDK();
4866
- const config = useWebSDKConfig();
4867
- const { product } = useProduct();
4868
- const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
4869
- const [postings, setPostings] = React.useState([]);
4870
- const [loading, setLoading] = React.useState(true);
4871
- const [error, setError] = React.useState(null);
4872
- const fetchPostings = React.useCallback(async () => {
4873
- setLoading(true);
4874
- setError(null);
4875
- try {
4876
- const items = await fetchPublicJobPostings(client, {
4877
- productId: resolvedProductId,
4878
- limit: options?.limit,
4879
- offset: options?.offset
4880
- });
4881
- setPostings(items);
4882
- } catch (err) {
4883
- setError(
4884
- err instanceof Error ? err.message : "Failed to fetch job postings"
4885
- );
4886
- setPostings([]);
4887
- } finally {
4888
- setLoading(false);
4889
- }
4890
- }, [client, resolvedProductId, options?.limit, options?.offset]);
4891
- React.useEffect(() => {
4892
- fetchPostings();
4893
- }, [fetchPostings]);
4894
- return { postings, loading, error, refetch: fetchPostings };
4895
- }
4896
- function usePublicJobPostingsForSubmission(options) {
4897
- const client = useWebSDK();
4898
- const config = useWebSDKConfig();
4899
- const { product } = useProduct();
4900
- const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
4901
- const [postings, setPostings] = React.useState([]);
4902
- const [loading, setLoading] = React.useState(true);
4903
- const [error, setError] = React.useState(null);
4904
- const fetchPostings = React.useCallback(async () => {
4905
- setLoading(true);
4906
- setError(null);
4907
- try {
4908
- const items = await fetchPublicJobPostingsForSubmission(client, {
4909
- productId: resolvedProductId,
4910
- limit: options?.limit,
4911
- offset: options?.offset
4912
- });
4913
- setPostings(items);
4914
- } catch (err) {
4915
- setError(
4916
- err instanceof Error ? err.message : "Failed to fetch job postings"
4917
- );
4918
- setPostings([]);
4919
- } finally {
4920
- setLoading(false);
4921
- }
4922
- }, [client, resolvedProductId, options?.limit, options?.offset]);
4923
- React.useEffect(() => {
4924
- fetchPostings();
4925
- }, [fetchPostings]);
4926
- return { postings, loading, error, refetch: fetchPostings };
4927
- }
4928
- function usePublicJobPosting(slug, options) {
4929
- const client = useWebSDK();
4930
- const config = useWebSDKConfig();
4931
- const { product } = useProduct();
4932
- const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
4933
- const [posting, setPosting] = React.useState(null);
4934
- const [loading, setLoading] = React.useState(true);
4935
- const [error, setError] = React.useState(null);
4936
- const fetchPosting = React.useCallback(async () => {
4937
- if (!slug) {
4938
- setPosting(null);
4939
- setLoading(false);
4940
- return;
4941
- }
4942
- setLoading(true);
4943
- setError(null);
4944
- try {
4945
- const item = await fetchPublicJobPosting(client, slug, {
4946
- productId: resolvedProductId
4947
- });
4948
- setPosting(item);
4949
- } catch (err) {
4950
- setError(
4951
- err instanceof Error ? err.message : "Failed to fetch job posting"
4952
- );
4953
- setPosting(null);
4954
- } finally {
4955
- setLoading(false);
4956
- }
4957
- }, [client, resolvedProductId, slug]);
4958
- React.useEffect(() => {
4959
- fetchPosting();
4960
- }, [fetchPosting]);
4961
- return { posting, loading, error, refetch: fetchPosting };
4962
- }
4963
4747
  var PROSE_CSS = `
4964
4748
  .boff-prose {
4965
4749
  max-width: none;
@@ -5136,32 +4920,1972 @@ var PROSE_CSS = `
5136
4920
  function ProseStyles() {
5137
4921
  return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: PROSE_CSS } });
5138
4922
  }
5139
- var BLOG_POSTS_QUERY = `
5140
- query BlogPosts($productId: String!, $limit: Int, $offset: Int) {
5141
- publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
5142
- items {
5143
- id
5144
- title
4923
+
4924
+ // src/components/explore-types.ts
4925
+ var EXPLORE_MESSAGE_FIELDS = (
4926
+ /* GraphQL */
4927
+ `
4928
+ id
4929
+ conversationId
4930
+ role
4931
+ status
4932
+ content
4933
+ partialContent
4934
+ activity
4935
+ references {
4936
+ url
4937
+ title
4938
+ description
4939
+ product
4940
+ imageUrl
4941
+ }
4942
+ ctas {
4943
+ kind
4944
+ label
4945
+ url
4946
+ product
4947
+ }
4948
+ errorCode
4949
+ createdAt
4950
+ completedAt
4951
+ `
4952
+ );
4953
+ var EXPLORE_CATALOG_QUERY = (
4954
+ /* GraphQL */
4955
+ `
4956
+ query ExploreCatalog($productSlug: String) {
4957
+ exploreCatalog(productSlug: $productSlug) {
4958
+ enabled
4959
+ products {
5145
4960
  slug
5146
- lastUpdated
5147
- tags
5148
- seoDescription
5149
- excerpt
5150
- thumbnail
5151
- author
5152
- publishedAt
5153
- readingTime
5154
- featured
5155
- category
4961
+ name
4962
+ tagline
4963
+ website
5156
4964
  }
5157
- total
5158
- hasMore
4965
+ examplePrompts
4966
+ welcomeTitle
4967
+ welcomeBody
4968
+ limits {
4969
+ maxMessageLength
4970
+ maxTurnsPerConversation
4971
+ messagesPerDay
4972
+ historyTurns
4973
+ }
4974
+ captchaRequired
4975
+ recaptchaSiteKey
5159
4976
  }
5160
4977
  }
5161
- `;
5162
- var BLOG_POST_QUERY = `
5163
- query BlogPost($slug: String!, $productId: String) {
5164
- publicContentPage(slug: $slug, productId: $productId) {
4978
+ `
4979
+ );
4980
+ var EXPLORE_MESSAGE_QUERY = (
4981
+ /* GraphQL */
4982
+ `
4983
+ query ExploreMessage($conversationToken: String!, $id: ID!) {
4984
+ exploreMessage(conversationToken: $conversationToken, id: $id) {
4985
+ ${EXPLORE_MESSAGE_FIELDS}
4986
+ }
4987
+ }
4988
+ `
4989
+ );
4990
+ var SEND_EXPLORE_MESSAGE_MUTATION = (
4991
+ /* GraphQL */
4992
+ `
4993
+ mutation SendExploreMessage($input: SendExploreMessageInput!) {
4994
+ sendExploreMessage(input: $input) {
4995
+ conversation {
4996
+ id
4997
+ token
4998
+ productSlug
4999
+ turnCount
5000
+ createdAt
5001
+ }
5002
+ userMessage {
5003
+ ${EXPLORE_MESSAGE_FIELDS}
5004
+ }
5005
+ assistantMessage {
5006
+ ${EXPLORE_MESSAGE_FIELDS}
5007
+ }
5008
+ remainingToday
5009
+ }
5010
+ }
5011
+ `
5012
+ );
5013
+
5014
+ // src/data/products.ts
5015
+ var ECOSYSTEM_PRODUCTS = [
5016
+ {
5017
+ slug: "workspaces",
5018
+ name: "Workspaces",
5019
+ websiteUrl: "https://burdenoff.com"
5020
+ },
5021
+ {
5022
+ slug: "vibecontrols",
5023
+ name: "VibeControls",
5024
+ websiteUrl: "https://vibecontrols.com"
5025
+ },
5026
+ {
5027
+ slug: "fluidgrids",
5028
+ name: "FluidGrids",
5029
+ websiteUrl: "https://fluidgrids.com"
5030
+ },
5031
+ // Botlit ships on botlit.ai — botlit.com does not resolve.
5032
+ { slug: "botlit", name: "Botlit", websiteUrl: "https://www.botlit.ai" },
5033
+ {
5034
+ slug: "bigconsole",
5035
+ name: "BigConsole",
5036
+ websiteUrl: "https://bigconsole.com"
5037
+ },
5038
+ {
5039
+ slug: "semanticfed",
5040
+ name: "SemanticFed",
5041
+ websiteUrl: "https://semanticfed.com"
5042
+ },
5043
+ {
5044
+ slug: "intelwatchtower",
5045
+ name: "IntelWatchtower",
5046
+ websiteUrl: "https://intelwatchtower.com"
5047
+ },
5048
+ { slug: "buildmyiq", name: "BuildMyIQ", websiteUrl: "https://buildmyiq.com" },
5049
+ {
5050
+ slug: "assethandler",
5051
+ name: "AssetHandler",
5052
+ websiteUrl: "https://assethandler.com"
5053
+ },
5054
+ {
5055
+ slug: "crewfoundry",
5056
+ name: "CrewFoundry",
5057
+ websiteUrl: "https://crewfoundry.com"
5058
+ },
5059
+ {
5060
+ slug: "planmagnet",
5061
+ name: "PlanMagnet",
5062
+ websiteUrl: "https://planmagnet.com"
5063
+ },
5064
+ {
5065
+ slug: "eventfullymanaged",
5066
+ name: "EventfullyManaged",
5067
+ websiteUrl: "https://eventfullymanaged.com"
5068
+ },
5069
+ {
5070
+ slug: "headshotmarketing",
5071
+ name: "HeadshotMarketing",
5072
+ websiteUrl: "https://headshotmarketing.com"
5073
+ },
5074
+ {
5075
+ slug: "technospam",
5076
+ name: "TechnoSpam",
5077
+ websiteUrl: "https://technospam.com"
5078
+ },
5079
+ { slug: "kadaikodi", name: "Kadaikodi", websiteUrl: "https://kadaikodi.com" },
5080
+ {
5081
+ slug: "movethewheels",
5082
+ name: "MoveTheWheels",
5083
+ websiteUrl: "https://movethewheels.com"
5084
+ },
5085
+ {
5086
+ slug: "healthybowl",
5087
+ name: "Healthy Bowl",
5088
+ websiteUrl: "https://gethealthybowl.com"
5089
+ },
5090
+ { slug: "ggnomad", name: "GGNomad", websiteUrl: "https://ggnomad.com" },
5091
+ {
5092
+ slug: "timecampus",
5093
+ name: "TimeCampus",
5094
+ websiteUrl: "https://timecampus.com"
5095
+ },
5096
+ {
5097
+ slug: "manufacturedops",
5098
+ name: "ManufacturedOps",
5099
+ websiteUrl: "https://manufacturedops.com"
5100
+ },
5101
+ {
5102
+ slug: "theglobalfuel",
5103
+ name: "TheGlobalFuel",
5104
+ websiteUrl: "https://theglobalfuel.com"
5105
+ },
5106
+ {
5107
+ slug: "housingvista",
5108
+ name: "HousingVista",
5109
+ websiteUrl: "https://housingvista.com"
5110
+ },
5111
+ {
5112
+ slug: "proserviceworld",
5113
+ name: "ProServiceWorld",
5114
+ websiteUrl: "https://proserviceworld.com"
5115
+ },
5116
+ {
5117
+ slug: "mybodyshield",
5118
+ name: "MyBodyShield",
5119
+ websiteUrl: "https://mybodyshield.com"
5120
+ },
5121
+ {
5122
+ slug: "artistrybase",
5123
+ name: "ArtistryBase",
5124
+ websiteUrl: "https://artistrybase.com"
5125
+ },
5126
+ {
5127
+ slug: "theculturepost",
5128
+ name: "TheCulturePost",
5129
+ websiteUrl: "https://theculturepost.com"
5130
+ },
5131
+ { slug: "collabkin", name: "CollabKin", websiteUrl: "https://collabkin.com" },
5132
+ {
5133
+ slug: "comradecircle",
5134
+ name: "ComradeCircle",
5135
+ websiteUrl: "https://comradecircle.com"
5136
+ },
5137
+ {
5138
+ slug: "brainyrich",
5139
+ name: "BrainyRich",
5140
+ websiteUrl: "https://brainyrich.com"
5141
+ },
5142
+ {
5143
+ slug: "cosmicintersection",
5144
+ name: "CosmicIntersection",
5145
+ websiteUrl: "https://cosmicintersection.com"
5146
+ },
5147
+ {
5148
+ slug: "govcitizenhub",
5149
+ name: "GovCitizenHub",
5150
+ websiteUrl: "https://govcitizenhub.com"
5151
+ },
5152
+ {
5153
+ slug: "adaptercloud",
5154
+ name: "AdapterCloud",
5155
+ websiteUrl: "https://adaptercloud.com"
5156
+ },
5157
+ {
5158
+ slug: "subscriberbot",
5159
+ name: "Subscriber Bot",
5160
+ websiteUrl: "https://subscriberbot.com"
5161
+ },
5162
+ {
5163
+ slug: "hookmovies",
5164
+ name: "HookMovies",
5165
+ websiteUrl: "https://hookmovies.com"
5166
+ },
5167
+ {
5168
+ slug: "coolandlovely",
5169
+ name: "CoolAndLovely",
5170
+ websiteUrl: "https://coolandlovely.com"
5171
+ },
5172
+ {
5173
+ slug: "atheletebridge",
5174
+ name: "AthleteBridge",
5175
+ websiteUrl: "https://atheletebridge.com"
5176
+ },
5177
+ {
5178
+ slug: "labsofscience",
5179
+ name: "LabsOfScience",
5180
+ websiteUrl: "https://labsofscience.com"
5181
+ },
5182
+ {
5183
+ slug: "ecoimpacthub",
5184
+ name: "EcoImpactHub",
5185
+ websiteUrl: "https://ecoimpacthub.com"
5186
+ }
5187
+ ];
5188
+
5189
+ // src/utils/recaptcha-v3.ts
5190
+ var SCRIPT_ID = "boff-recaptcha-v3";
5191
+ var SCRIPT_SRC = "https://www.google.com/recaptcha/api.js?render=";
5192
+ var EXPLORE_RECAPTCHA_ACTION = "explore_chat";
5193
+ var loaders = /* @__PURE__ */ new Map();
5194
+ function isBrowser() {
5195
+ return typeof window !== "undefined" && typeof document !== "undefined";
5196
+ }
5197
+ function loadRecaptchaV3(siteKey) {
5198
+ if (!siteKey) {
5199
+ return Promise.reject(new Error("reCAPTCHA site key is required"));
5200
+ }
5201
+ if (!isBrowser()) {
5202
+ return Promise.reject(
5203
+ new Error("reCAPTCHA v3 can only be loaded in a browser")
5204
+ );
5205
+ }
5206
+ const existing = loaders.get(siteKey);
5207
+ if (existing) return existing;
5208
+ const promise = new Promise((resolve, reject) => {
5209
+ const ready = () => {
5210
+ const api = window.grecaptcha;
5211
+ if (!api) {
5212
+ reject(new Error("reCAPTCHA loaded but grecaptcha is unavailable"));
5213
+ return;
5214
+ }
5215
+ api.ready(() => resolve(api));
5216
+ };
5217
+ if (window.grecaptcha) {
5218
+ ready();
5219
+ return;
5220
+ }
5221
+ const scriptId = `${SCRIPT_ID}-${siteKey}`;
5222
+ const found = document.getElementById(scriptId);
5223
+ if (found) {
5224
+ found.addEventListener("load", ready, { once: true });
5225
+ found.addEventListener(
5226
+ "error",
5227
+ () => reject(new Error("Failed to load reCAPTCHA v3")),
5228
+ { once: true }
5229
+ );
5230
+ return;
5231
+ }
5232
+ const script = document.createElement("script");
5233
+ script.id = scriptId;
5234
+ script.src = `${SCRIPT_SRC}${encodeURIComponent(siteKey)}`;
5235
+ script.async = true;
5236
+ script.defer = true;
5237
+ script.addEventListener("load", ready, { once: true });
5238
+ script.addEventListener(
5239
+ "error",
5240
+ () => {
5241
+ loaders.delete(siteKey);
5242
+ reject(new Error("Failed to load reCAPTCHA v3"));
5243
+ },
5244
+ { once: true }
5245
+ );
5246
+ document.head.appendChild(script);
5247
+ });
5248
+ loaders.set(siteKey, promise);
5249
+ return promise;
5250
+ }
5251
+ async function getRecaptchaV3Token(siteKey, action = EXPLORE_RECAPTCHA_ACTION) {
5252
+ const api = await loadRecaptchaV3(siteKey);
5253
+ return api.execute(siteKey, { action });
5254
+ }
5255
+
5256
+ // src/hooks/use-explore-chat.ts
5257
+ var DEFAULT_STORAGE_KEY = "boff.explore.v1";
5258
+ var DEFAULT_POLL_INTERVAL_MS = 1500;
5259
+ var DEFAULT_POLL_TIMEOUT_MS = 9e4;
5260
+ var POLL_REQUEST_TIMEOUT_MS = 1e4;
5261
+ var SEND_REQUEST_TIMEOUT_MS = 18e3;
5262
+ var MAX_CONSECUTIVE_POLL_FAILURES = 4;
5263
+ var MAX_PERSISTED_MESSAGES = 40;
5264
+ var FALLBACK_EXPLORE_LIMITS = {
5265
+ maxMessageLength: 2e3,
5266
+ maxTurnsPerConversation: 20,
5267
+ messagesPerDay: 100,
5268
+ historyTurns: 6
5269
+ };
5270
+ var DEFAULT_EXPLORE_EXAMPLE_PROMPTS = [
5271
+ "What does this product actually do?",
5272
+ "Which Burdenoff product fits my team?",
5273
+ "How do I get started, step by step?",
5274
+ "What does it integrate with?"
5275
+ ];
5276
+ var FALLBACK_WELCOME_TITLE = "Ask anything about our products";
5277
+ var FALLBACK_WELCOME_BODY = "Get straight answers drawn from our product documentation and public pages \u2014 with links back to the exact page each answer came from.";
5278
+ function buildFallbackCatalog(examplePrompts) {
5279
+ return {
5280
+ enabled: true,
5281
+ products: ECOSYSTEM_PRODUCTS.map((p) => ({
5282
+ slug: p.slug,
5283
+ name: p.name,
5284
+ tagline: null,
5285
+ website: p.websiteUrl
5286
+ })),
5287
+ examplePrompts: examplePrompts?.length ? examplePrompts : DEFAULT_EXPLORE_EXAMPLE_PROMPTS,
5288
+ welcomeTitle: FALLBACK_WELCOME_TITLE,
5289
+ welcomeBody: FALLBACK_WELCOME_BODY,
5290
+ limits: FALLBACK_EXPLORE_LIMITS,
5291
+ captchaRequired: false,
5292
+ recaptchaSiteKey: null
5293
+ };
5294
+ }
5295
+ function normalizeCatalog(raw, fallback) {
5296
+ return {
5297
+ enabled: raw.enabled !== false,
5298
+ products: raw.products?.length ? raw.products : fallback.products,
5299
+ examplePrompts: raw.examplePrompts?.length ? raw.examplePrompts : fallback.examplePrompts,
5300
+ welcomeTitle: raw.welcomeTitle || fallback.welcomeTitle,
5301
+ welcomeBody: raw.welcomeBody || fallback.welcomeBody,
5302
+ limits: {
5303
+ maxMessageLength: raw.limits?.maxMessageLength || fallback.limits.maxMessageLength,
5304
+ maxTurnsPerConversation: raw.limits?.maxTurnsPerConversation || fallback.limits.maxTurnsPerConversation,
5305
+ messagesPerDay: raw.limits?.messagesPerDay || fallback.limits.messagesPerDay,
5306
+ historyTurns: raw.limits?.historyTurns || fallback.limits.historyTurns
5307
+ },
5308
+ captchaRequired: raw.captchaRequired === true,
5309
+ recaptchaSiteKey: raw.recaptchaSiteKey ?? null
5310
+ };
5311
+ }
5312
+ var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
5313
+ "RATE_LIMITED",
5314
+ "HTTP_429",
5315
+ "EXPLORE_GLOBAL_LIMIT"
5316
+ ]);
5317
+ var CAPTCHA_CODES = /* @__PURE__ */ new Set(["CAPTCHA_FAILED", "CAPTCHA_REQUIRED"]);
5318
+ var NETWORK_CODES = /* @__PURE__ */ new Set([
5319
+ "NETWORK_ERROR",
5320
+ "UPSTREAM_UNAVAILABLE",
5321
+ "CONVERSATION_BUSY",
5322
+ "PROVIDER_ERROR",
5323
+ "QUEUE_FULL"
5324
+ ]);
5325
+ var VALIDATION_CODES = /* @__PURE__ */ new Set([
5326
+ "MESSAGE_TOO_LONG",
5327
+ "MESSAGE_EMPTY",
5328
+ "CONVERSATION_LIMIT",
5329
+ "INVALID_INPUT"
5330
+ ]);
5331
+ function classifyExploreError(code, serverMessage) {
5332
+ const c = code ?? "UNKNOWN";
5333
+ if (RATE_LIMIT_CODES.has(c)) {
5334
+ return {
5335
+ kind: "rate-limit",
5336
+ code: c,
5337
+ message: "You've reached the message limit for now. Please try again later.",
5338
+ retryable: false
5339
+ };
5340
+ }
5341
+ if (CAPTCHA_CODES.has(c)) {
5342
+ return {
5343
+ kind: "captcha",
5344
+ code: c,
5345
+ message: "We couldn't verify that you're human. Please try sending that again.",
5346
+ retryable: true
5347
+ };
5348
+ }
5349
+ if (c === "TIMEOUT" || c === "TOOL_BUDGET") {
5350
+ return {
5351
+ kind: "timeout",
5352
+ code: c,
5353
+ message: "That answer took too long to come back. Try asking again.",
5354
+ retryable: true
5355
+ };
5356
+ }
5357
+ if (NETWORK_CODES.has(c)) {
5358
+ return {
5359
+ kind: "network",
5360
+ code: c,
5361
+ message: "We couldn't reach the assistant just now. Check your connection and try again.",
5362
+ retryable: true
5363
+ };
5364
+ }
5365
+ if (c === "EXPLORE_DISABLED") {
5366
+ return {
5367
+ kind: "disabled",
5368
+ code: c,
5369
+ message: "Explore is unavailable at the moment. Please check back soon.",
5370
+ retryable: false
5371
+ };
5372
+ }
5373
+ if (VALIDATION_CODES.has(c)) {
5374
+ return {
5375
+ kind: "validation",
5376
+ code: c,
5377
+ message: serverMessage || "That message couldn't be sent.",
5378
+ retryable: false
5379
+ };
5380
+ }
5381
+ if (c === "EMPTY_ANSWER") {
5382
+ return {
5383
+ kind: "unknown",
5384
+ code: c,
5385
+ message: "No answer came back for that one. Try rephrasing, or ask something more specific.",
5386
+ retryable: true
5387
+ };
5388
+ }
5389
+ return {
5390
+ kind: "unknown",
5391
+ code: c,
5392
+ message: serverMessage || "Something went wrong. Please try again.",
5393
+ retryable: true
5394
+ };
5395
+ }
5396
+ function readPersisted(key) {
5397
+ if (typeof window === "undefined") return null;
5398
+ try {
5399
+ const raw = window.sessionStorage.getItem(key);
5400
+ if (!raw) return null;
5401
+ const parsed = JSON.parse(raw);
5402
+ if (!parsed || typeof parsed !== "object") return null;
5403
+ const record = parsed;
5404
+ return {
5405
+ conversationToken: typeof record.conversationToken === "string" ? record.conversationToken : null,
5406
+ messages: Array.isArray(record.messages) ? record.messages.slice(-MAX_PERSISTED_MESSAGES) : [],
5407
+ focusProduct: typeof record.focusProduct === "string" ? record.focusProduct : null
5408
+ };
5409
+ } catch {
5410
+ return null;
5411
+ }
5412
+ }
5413
+ function visibleText(message) {
5414
+ return message.content || message.partialContent || "";
5415
+ }
5416
+ function countUserTurns(messages) {
5417
+ return messages.filter((m) => m.role === "USER").length;
5418
+ }
5419
+ function useExploreChat(options = {}) {
5420
+ const {
5421
+ productSlug,
5422
+ pageUrl,
5423
+ locale,
5424
+ examplePrompts,
5425
+ pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
5426
+ pollTimeoutMs = DEFAULT_POLL_TIMEOUT_MS,
5427
+ storageKey = DEFAULT_STORAGE_KEY,
5428
+ persist = true,
5429
+ onSend
5430
+ } = options;
5431
+ const client = useWebSDK();
5432
+ const promptKey = JSON.stringify(examplePrompts ?? []);
5433
+ const fallbackCatalog = React.useMemo(
5434
+ () => buildFallbackCatalog(JSON.parse(promptKey)),
5435
+ [promptKey]
5436
+ );
5437
+ const [catalog, setCatalog] = React.useState(fallbackCatalog);
5438
+ const [messages, setMessages] = React.useState([]);
5439
+ const [phase, setPhase] = React.useState("loading");
5440
+ const [error, setError] = React.useState(null);
5441
+ const [remainingToday, setRemainingToday] = React.useState(null);
5442
+ const [focusProduct, setFocusProductState] = React.useState(null);
5443
+ const [turnCount, setTurnCount] = React.useState(0);
5444
+ const [hydrated, setHydrated] = React.useState(false);
5445
+ const mountedRef = React.useRef(true);
5446
+ const pollGenerationRef = React.useRef(0);
5447
+ const pollTimerRef = React.useRef(null);
5448
+ const conversationTokenRef = React.useRef(null);
5449
+ const catalogRef = React.useRef(fallbackCatalog);
5450
+ const turnCountRef = React.useRef(0);
5451
+ const focusProductRef = React.useRef(null);
5452
+ const lastSentTextRef = React.useRef(null);
5453
+ const inFlightRef = React.useRef(false);
5454
+ catalogRef.current = catalog;
5455
+ const clearPollTimer = React.useCallback(() => {
5456
+ if (pollTimerRef.current !== null) {
5457
+ clearTimeout(pollTimerRef.current);
5458
+ pollTimerRef.current = null;
5459
+ }
5460
+ }, []);
5461
+ const cancelPolling = React.useCallback(() => {
5462
+ pollGenerationRef.current += 1;
5463
+ clearPollTimer();
5464
+ }, [clearPollTimer]);
5465
+ React.useEffect(() => {
5466
+ mountedRef.current = true;
5467
+ return () => {
5468
+ mountedRef.current = false;
5469
+ pollGenerationRef.current += 1;
5470
+ if (pollTimerRef.current !== null) {
5471
+ clearTimeout(pollTimerRef.current);
5472
+ pollTimerRef.current = null;
5473
+ }
5474
+ };
5475
+ }, []);
5476
+ React.useEffect(() => {
5477
+ if (!persist) {
5478
+ setHydrated(true);
5479
+ return;
5480
+ }
5481
+ const stored = readPersisted(storageKey);
5482
+ if (stored) {
5483
+ conversationTokenRef.current = stored.conversationToken;
5484
+ focusProductRef.current = stored.focusProduct;
5485
+ const settled = stored.messages.flatMap((m) => {
5486
+ if (m.role !== "ASSISTANT") return [m];
5487
+ if (m.status !== "PENDING" && m.status !== "RUNNING") return [m];
5488
+ const text = visibleText(m);
5489
+ if (!text) return [];
5490
+ return [{ ...m, status: "COMPLETED", content: text }];
5491
+ });
5492
+ setMessages(settled);
5493
+ setFocusProductState(stored.focusProduct);
5494
+ const turns = countUserTurns(settled);
5495
+ turnCountRef.current = turns;
5496
+ setTurnCount(turns);
5497
+ }
5498
+ setHydrated(true);
5499
+ }, [persist, storageKey]);
5500
+ React.useEffect(() => {
5501
+ if (!persist || !hydrated || typeof window === "undefined") return;
5502
+ try {
5503
+ const payload = {
5504
+ conversationToken: conversationTokenRef.current,
5505
+ messages: messages.slice(-MAX_PERSISTED_MESSAGES),
5506
+ focusProduct
5507
+ };
5508
+ if (!payload.conversationToken && payload.messages.length === 0 && !payload.focusProduct) {
5509
+ window.sessionStorage.removeItem(storageKey);
5510
+ return;
5511
+ }
5512
+ window.sessionStorage.setItem(storageKey, JSON.stringify(payload));
5513
+ } catch {
5514
+ }
5515
+ }, [persist, hydrated, storageKey, messages, focusProduct]);
5516
+ React.useEffect(() => {
5517
+ let cancelled = false;
5518
+ const load = async () => {
5519
+ const res = await client.query(
5520
+ EXPLORE_CATALOG_QUERY,
5521
+ { productSlug: productSlug ?? null }
5522
+ );
5523
+ if (cancelled || !mountedRef.current) return;
5524
+ const raw = res.data?.exploreCatalog;
5525
+ if (res.errors?.length || !raw) {
5526
+ console.warn(
5527
+ "[WebSDK] exploreCatalog unavailable \u2014 using the built-in fallback catalog",
5528
+ res.errors?.[0]?.message
5529
+ );
5530
+ setPhase((prev) => prev === "loading" ? "ready" : prev);
5531
+ return;
5532
+ }
5533
+ const next = normalizeCatalog(raw, fallbackCatalog);
5534
+ setCatalog(next);
5535
+ catalogRef.current = next;
5536
+ setPhase((prev) => {
5537
+ if (!next.enabled) return "disabled";
5538
+ return prev === "loading" ? "ready" : prev;
5539
+ });
5540
+ };
5541
+ void load();
5542
+ return () => {
5543
+ cancelled = true;
5544
+ };
5545
+ }, [client, productSlug, fallbackCatalog]);
5546
+ const startPolling = React.useCallback(
5547
+ (conversationToken, messageId) => {
5548
+ pollGenerationRef.current += 1;
5549
+ const generation = pollGenerationRef.current;
5550
+ const startedAt = Date.now();
5551
+ let consecutiveFailures = 0;
5552
+ const isStale = () => !mountedRef.current || generation !== pollGenerationRef.current;
5553
+ const finishWithError = (chatError) => {
5554
+ inFlightRef.current = false;
5555
+ setMessages(
5556
+ (prev) => prev.flatMap((m) => {
5557
+ if (m.id !== messageId) return [m];
5558
+ const text = visibleText(m);
5559
+ if (!text) return [];
5560
+ return [{ ...m, status: "FAILED", content: text }];
5561
+ })
5562
+ );
5563
+ setError(chatError);
5564
+ setPhase(chatError.kind === "rate-limit" ? "limited" : "error");
5565
+ };
5566
+ const tick = async () => {
5567
+ if (isStale()) return;
5568
+ if (Date.now() - startedAt > pollTimeoutMs) {
5569
+ finishWithError(classifyExploreError("TIMEOUT"));
5570
+ return;
5571
+ }
5572
+ const res = await client.query(
5573
+ EXPLORE_MESSAGE_QUERY,
5574
+ { conversationToken, id: messageId },
5575
+ { cache: false, timeoutMs: POLL_REQUEST_TIMEOUT_MS }
5576
+ );
5577
+ if (isStale()) return;
5578
+ const first = res.errors?.[0];
5579
+ if (first) {
5580
+ const code = typeof first.extensions?.code === "string" ? first.extensions.code : void 0;
5581
+ const classified = classifyExploreError(code, first.message);
5582
+ if (!classified.retryable) {
5583
+ finishWithError(classified);
5584
+ return;
5585
+ }
5586
+ consecutiveFailures += 1;
5587
+ if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
5588
+ finishWithError(classified);
5589
+ return;
5590
+ }
5591
+ pollTimerRef.current = setTimeout(() => void tick(), pollIntervalMs);
5592
+ return;
5593
+ }
5594
+ const message = res.data?.exploreMessage;
5595
+ if (!message) {
5596
+ consecutiveFailures += 1;
5597
+ if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
5598
+ finishWithError(classifyExploreError("NOT_FOUND"));
5599
+ return;
5600
+ }
5601
+ pollTimerRef.current = setTimeout(() => void tick(), pollIntervalMs);
5602
+ return;
5603
+ }
5604
+ consecutiveFailures = 0;
5605
+ setMessages(
5606
+ (prev) => prev.map((m) => m.id === messageId ? message : m)
5607
+ );
5608
+ if (message.status === "COMPLETED") {
5609
+ inFlightRef.current = false;
5610
+ setError(null);
5611
+ setPhase("ready");
5612
+ return;
5613
+ }
5614
+ if (message.status === "FAILED") {
5615
+ finishWithError(
5616
+ classifyExploreError(message.errorCode ?? "UPSTREAM_UNAVAILABLE")
5617
+ );
5618
+ return;
5619
+ }
5620
+ setPhase("streaming");
5621
+ pollTimerRef.current = setTimeout(() => void tick(), pollIntervalMs);
5622
+ };
5623
+ pollTimerRef.current = setTimeout(() => void tick(), pollIntervalMs);
5624
+ },
5625
+ [client, pollIntervalMs, pollTimeoutMs]
5626
+ );
5627
+ const submit = React.useCallback(
5628
+ async (text, optimisticId, captchaAttempt) => {
5629
+ const activeCatalog = catalogRef.current;
5630
+ let recaptchaToken;
5631
+ if (activeCatalog.captchaRequired && activeCatalog.recaptchaSiteKey) {
5632
+ try {
5633
+ recaptchaToken = await getRecaptchaV3Token(
5634
+ activeCatalog.recaptchaSiteKey
5635
+ );
5636
+ } catch (err) {
5637
+ console.warn("[WebSDK] reCAPTCHA v3 token could not be minted", err);
5638
+ }
5639
+ }
5640
+ const input = {
5641
+ message: text,
5642
+ ...conversationTokenRef.current ? { conversationToken: conversationTokenRef.current } : {},
5643
+ ...focusProductRef.current || productSlug ? { productSlug: focusProductRef.current ?? productSlug } : {},
5644
+ ...recaptchaToken ? { recaptchaToken } : {},
5645
+ ...locale ? { locale } : {},
5646
+ ...pageUrl ? { pageUrl } : {}
5647
+ };
5648
+ const res = await client.mutate(
5649
+ SEND_EXPLORE_MESSAGE_MUTATION,
5650
+ { input },
5651
+ { timeoutMs: SEND_REQUEST_TIMEOUT_MS }
5652
+ );
5653
+ if (!mountedRef.current) return;
5654
+ const first = res.errors?.[0];
5655
+ const result = res.data?.sendExploreMessage;
5656
+ if (first || !result) {
5657
+ const code = typeof first?.extensions?.code === "string" ? first.extensions.code : void 0;
5658
+ const classified = classifyExploreError(code, first?.message);
5659
+ if (classified.kind === "captcha" && captchaAttempt === 0) {
5660
+ await submit(text, optimisticId, 1);
5661
+ return;
5662
+ }
5663
+ inFlightRef.current = false;
5664
+ setError(classified);
5665
+ setPhase(classified.kind === "rate-limit" ? "limited" : "error");
5666
+ return;
5667
+ }
5668
+ conversationTokenRef.current = result.conversation.token;
5669
+ turnCountRef.current = result.conversation.turnCount;
5670
+ setTurnCount(result.conversation.turnCount);
5671
+ setRemainingToday(result.remainingToday ?? null);
5672
+ const userMessage = result.userMessage;
5673
+ const assistantMessage = result.assistantMessage;
5674
+ setMessages((prev) => {
5675
+ const replaced = prev.some((m) => m.id === optimisticId) ? prev.map((m) => m.id === optimisticId ? userMessage : m) : [...prev, userMessage];
5676
+ return [...replaced, assistantMessage];
5677
+ });
5678
+ if (assistantMessage.status === "COMPLETED" || assistantMessage.status === "FAILED") {
5679
+ inFlightRef.current = false;
5680
+ if (assistantMessage.status === "FAILED") {
5681
+ const classified = classifyExploreError(
5682
+ assistantMessage.errorCode ?? "UPSTREAM_UNAVAILABLE"
5683
+ );
5684
+ setError(classified);
5685
+ setPhase("error");
5686
+ } else {
5687
+ setPhase("ready");
5688
+ }
5689
+ return;
5690
+ }
5691
+ setPhase("streaming");
5692
+ startPolling(result.conversation.token, assistantMessage.id);
5693
+ },
5694
+ [client, locale, pageUrl, productSlug, startPolling]
5695
+ );
5696
+ const send = React.useCallback(
5697
+ async (message) => {
5698
+ const text = message.trim();
5699
+ if (!text) return;
5700
+ if (inFlightRef.current) return;
5701
+ const activeCatalog = catalogRef.current;
5702
+ if (!activeCatalog.enabled) {
5703
+ setError(classifyExploreError("EXPLORE_DISABLED"));
5704
+ setPhase("disabled");
5705
+ return;
5706
+ }
5707
+ if (text.length > activeCatalog.limits.maxMessageLength) {
5708
+ setError({
5709
+ kind: "validation",
5710
+ code: "MESSAGE_TOO_LONG",
5711
+ message: `That message is ${text.length} characters \u2014 the limit is ${activeCatalog.limits.maxMessageLength}.`,
5712
+ retryable: false
5713
+ });
5714
+ return;
5715
+ }
5716
+ if (turnCountRef.current >= activeCatalog.limits.maxTurnsPerConversation) {
5717
+ setError({
5718
+ kind: "validation",
5719
+ code: "CONVERSATION_LIMIT",
5720
+ message: `This conversation has reached its ${activeCatalog.limits.maxTurnsPerConversation}-message limit. Start a new chat to keep going.`,
5721
+ retryable: false
5722
+ });
5723
+ return;
5724
+ }
5725
+ inFlightRef.current = true;
5726
+ lastSentTextRef.current = text;
5727
+ setError(null);
5728
+ setPhase("sending");
5729
+ cancelPolling();
5730
+ const optimistic = {
5731
+ id: makeOptimisticId(),
5732
+ conversationId: conversationTokenRef.current ?? "pending",
5733
+ role: "USER",
5734
+ status: "COMPLETED",
5735
+ content: text,
5736
+ partialContent: null,
5737
+ activity: [],
5738
+ references: [],
5739
+ ctas: [],
5740
+ errorCode: null,
5741
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5742
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
5743
+ };
5744
+ setMessages((prev) => [...prev, optimistic]);
5745
+ turnCountRef.current += 1;
5746
+ setTurnCount(turnCountRef.current);
5747
+ onSend?.(text);
5748
+ try {
5749
+ await submit(text, optimistic.id, 0);
5750
+ } catch (err) {
5751
+ if (!mountedRef.current) return;
5752
+ console.error("[WebSDK] Explore send failed", err);
5753
+ inFlightRef.current = false;
5754
+ setError(classifyExploreError("NETWORK_ERROR"));
5755
+ setPhase("error");
5756
+ }
5757
+ },
5758
+ [cancelPolling, onSend, submit]
5759
+ );
5760
+ const stop = React.useCallback(() => {
5761
+ cancelPolling();
5762
+ inFlightRef.current = false;
5763
+ setMessages(
5764
+ (prev) => prev.flatMap((m) => {
5765
+ if (m.role !== "ASSISTANT") return [m];
5766
+ if (m.status !== "PENDING" && m.status !== "RUNNING") return [m];
5767
+ const text = visibleText(m);
5768
+ if (!text) return [];
5769
+ return [
5770
+ {
5771
+ ...m,
5772
+ status: "COMPLETED",
5773
+ content: text,
5774
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
5775
+ }
5776
+ ];
5777
+ })
5778
+ );
5779
+ setPhase(
5780
+ (prev) => prev === "sending" || prev === "streaming" ? "ready" : prev
5781
+ );
5782
+ }, [cancelPolling]);
5783
+ const retry = React.useCallback(() => {
5784
+ const text = lastSentTextRef.current;
5785
+ if (!text) return;
5786
+ cancelPolling();
5787
+ inFlightRef.current = false;
5788
+ setMessages((prev) => {
5789
+ const next = [...prev];
5790
+ const last = next[next.length - 1];
5791
+ if (last?.role === "ASSISTANT" && last.status === "FAILED") next.pop();
5792
+ const prior = next[next.length - 1];
5793
+ if (prior?.role === "USER" && prior.content === text) next.pop();
5794
+ return next;
5795
+ });
5796
+ turnCountRef.current = Math.max(0, turnCountRef.current - 1);
5797
+ setTurnCount(turnCountRef.current);
5798
+ void send(text);
5799
+ }, [cancelPolling, send]);
5800
+ const reset = React.useCallback(() => {
5801
+ cancelPolling();
5802
+ inFlightRef.current = false;
5803
+ conversationTokenRef.current = null;
5804
+ lastSentTextRef.current = null;
5805
+ turnCountRef.current = 0;
5806
+ setTurnCount(0);
5807
+ setMessages([]);
5808
+ setError(null);
5809
+ setRemainingToday(null);
5810
+ setPhase(catalogRef.current.enabled ? "ready" : "disabled");
5811
+ if (typeof window !== "undefined") {
5812
+ try {
5813
+ window.sessionStorage.removeItem(storageKey);
5814
+ } catch {
5815
+ }
5816
+ }
5817
+ }, [cancelPolling, storageKey]);
5818
+ const setFocusProduct = React.useCallback((slug) => {
5819
+ focusProductRef.current = slug;
5820
+ setFocusProductState(slug);
5821
+ }, []);
5822
+ const activity = React.useMemo(() => {
5823
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
5824
+ const m = messages[i];
5825
+ if (!m || m.role !== "ASSISTANT") continue;
5826
+ if (m.status !== "PENDING" && m.status !== "RUNNING") return null;
5827
+ const lines = m.activity ?? [];
5828
+ return lines.length ? lines[lines.length - 1] ?? null : null;
5829
+ }
5830
+ return null;
5831
+ }, [messages]);
5832
+ const canSend = (phase === "ready" || phase === "error") && catalog.enabled && turnCount < catalog.limits.maxTurnsPerConversation;
5833
+ return {
5834
+ phase,
5835
+ catalog,
5836
+ messages,
5837
+ activity,
5838
+ remainingToday,
5839
+ error,
5840
+ focusProduct,
5841
+ setFocusProduct,
5842
+ send,
5843
+ stop,
5844
+ retry,
5845
+ reset,
5846
+ canSend
5847
+ };
5848
+ }
5849
+ var optimisticCounter = 0;
5850
+ function makeOptimisticId() {
5851
+ optimisticCounter += 1;
5852
+ return `boff-explore-local-${Date.now()}-${optimisticCounter}`;
5853
+ }
5854
+ var EXPLORE_CSS = `
5855
+ @keyframes boff-explore-pulse {
5856
+ 0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
5857
+ 40% { opacity: 1; transform: translateY(-2px); }
5858
+ }
5859
+ .boff-explore-dot {
5860
+ display: inline-block;
5861
+ width: 5px;
5862
+ height: 5px;
5863
+ border-radius: 9999px;
5864
+ background-color: hsl(var(--muted-foreground));
5865
+ animation: boff-explore-pulse 1.2s ease-in-out infinite;
5866
+ }
5867
+ @keyframes boff-explore-caret {
5868
+ 0%, 45% { opacity: 1; }
5869
+ 50%, 100% { opacity: 0; }
5870
+ }
5871
+ .boff-explore-caret {
5872
+ display: inline-block;
5873
+ width: 2px;
5874
+ height: 1em;
5875
+ margin-left: 2px;
5876
+ vertical-align: text-bottom;
5877
+ background-color: hsl(var(--primary));
5878
+ animation: boff-explore-caret 1s step-end infinite;
5879
+ }
5880
+ @media (prefers-reduced-motion: reduce) {
5881
+ .boff-explore-dot,
5882
+ .boff-explore-caret { animation: none; }
5883
+ .boff-explore-dot { opacity: 0.6; }
5884
+ }
5885
+ .boff-explore-scroll {
5886
+ scrollbar-width: thin;
5887
+ scrollbar-color: hsl(var(--border)) transparent;
5888
+ }
5889
+ .boff-explore-scroll::-webkit-scrollbar { width: 10px; }
5890
+ .boff-explore-scroll::-webkit-scrollbar-track { background: transparent; }
5891
+ .boff-explore-scroll::-webkit-scrollbar-thumb {
5892
+ background-color: hsl(var(--border));
5893
+ border: 3px solid transparent;
5894
+ border-radius: 9999px;
5895
+ background-clip: content-box;
5896
+ }
5897
+ `;
5898
+ function ExploreStyles() {
5899
+ return /* @__PURE__ */ jsxRuntime.jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
5900
+ }
5901
+ var CAPABILITIES = [
5902
+ {
5903
+ icon: lucideReact.BookOpenText,
5904
+ title: "Grounded in our own docs",
5905
+ body: "Every answer comes from published product pages and documentation \u2014 never invented."
5906
+ },
5907
+ {
5908
+ icon: lucideReact.Layers,
5909
+ title: "Compare across the ecosystem",
5910
+ body: "Ask which product fits your team, or how two of them work together."
5911
+ },
5912
+ {
5913
+ icon: lucideReact.Link2,
5914
+ title: "Straight to the right page",
5915
+ body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
5916
+ }
5917
+ ];
5918
+ var PRODUCT_CHIP_PREVIEW = 12;
5919
+ var CTA_VARIANTS = {
5920
+ CONTACT: "default",
5921
+ WAITLIST: "default",
5922
+ APP: "default",
5923
+ PRICING: "secondary",
5924
+ STORE: "secondary",
5925
+ PARTNERS: "secondary",
5926
+ DOCS: "outline",
5927
+ SERVICES: "outline",
5928
+ OTHER: "outline"
5929
+ };
5930
+ function normalizeOffset(offset) {
5931
+ if (offset === void 0) return "0px";
5932
+ return typeof offset === "number" ? `${offset}px` : offset;
5933
+ }
5934
+ function isInternalPath(url) {
5935
+ return url.startsWith("/") && !url.startsWith("//");
5936
+ }
5937
+ function hostnameOf(url) {
5938
+ try {
5939
+ return new URL(url).hostname.replace(/^www\./, "");
5940
+ } catch {
5941
+ return "";
5942
+ }
5943
+ }
5944
+ function initialOf(value, fallback) {
5945
+ const source = (value || fallback).trim();
5946
+ return source ? source.slice(0, 1).toUpperCase() : "?";
5947
+ }
5948
+ function ReferenceCard({ reference }) {
5949
+ const [imageFailed, setImageFailed] = React.useState(false);
5950
+ const host = hostnameOf(reference.url);
5951
+ const showImage = Boolean(reference.imageUrl) && !imageFailed;
5952
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5953
+ "a",
5954
+ {
5955
+ "data-boff-explore": "reference",
5956
+ href: reference.url,
5957
+ target: "_blank",
5958
+ rel: "noopener noreferrer",
5959
+ className: "group flex flex-col overflow-hidden rounded-xl border border-border bg-card transition-colors hover:border-primary/40 hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
5960
+ children: [
5961
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative aspect-[16/9] w-full overflow-hidden bg-muted", children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
5962
+ "img",
5963
+ {
5964
+ src: reference.imageUrl ?? "",
5965
+ alt: "",
5966
+ loading: "lazy",
5967
+ onError: () => setImageFailed(true),
5968
+ className: "h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
5969
+ }
5970
+ ) : (
5971
+ /* Graceful fallback: a token-derived gradient plate, so a missing or
5972
+ broken preview still reads as a deliberate card, not a hole. */
5973
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-primary/25 via-primary/5 to-secondary", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-2xl font-semibold text-primary/70", children: initialOf(reference.product, reference.title || host) }) })
5974
+ ) }),
5975
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-1.5 p-3", children: [
5976
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-sm font-semibold leading-snug text-card-foreground", children: reference.title || host || reference.url }),
5977
+ reference.description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "line-clamp-2 text-xs leading-relaxed text-muted-foreground", children: reference.description }) : null,
5978
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-auto flex items-center gap-2 pt-2", children: [
5979
+ reference.product ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground", children: reference.product }) : null,
5980
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-auto inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground", children: [
5981
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3 shrink-0" }),
5982
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: host })
5983
+ ] })
5984
+ ] })
5985
+ ] })
5986
+ ]
5987
+ }
5988
+ );
5989
+ }
5990
+ function CtaButton({
5991
+ cta,
5992
+ onNavigate
5993
+ }) {
5994
+ const variant = CTA_VARIANTS[cta.kind] ?? "outline";
5995
+ const internal = isInternalPath(cta.url);
5996
+ if (internal && onNavigate) {
5997
+ return /* @__PURE__ */ jsxRuntime.jsx(
5998
+ Button,
5999
+ {
6000
+ "data-boff-explore": "cta",
6001
+ "data-kind": cta.kind,
6002
+ type: "button",
6003
+ size: "sm",
6004
+ variant,
6005
+ onClick: () => onNavigate(cta.url),
6006
+ children: cta.label
6007
+ }
6008
+ );
6009
+ }
6010
+ return /* @__PURE__ */ jsxRuntime.jsx(Button, { asChild: true, size: "sm", variant, children: /* @__PURE__ */ jsxRuntime.jsxs(
6011
+ "a",
6012
+ {
6013
+ "data-boff-explore": "cta",
6014
+ "data-kind": cta.kind,
6015
+ href: cta.url,
6016
+ ...internal ? {} : { target: "_blank", rel: "noopener noreferrer" },
6017
+ children: [
6018
+ cta.label,
6019
+ internal ? null : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "ml-1 h-3 w-3" })
6020
+ ]
6021
+ }
6022
+ ) });
6023
+ }
6024
+ function ThinkingIndicator({ activity }) {
6025
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6026
+ "div",
6027
+ {
6028
+ "data-boff-explore": "thinking",
6029
+ className: "flex items-center gap-2.5 rounded-2xl border border-border bg-muted/60 px-3.5 py-2.5 text-sm text-muted-foreground",
6030
+ children: [
6031
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", "aria-hidden": "true", children: [
6032
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "boff-explore-dot" }),
6033
+ /* @__PURE__ */ jsxRuntime.jsx(
6034
+ "span",
6035
+ {
6036
+ className: "boff-explore-dot",
6037
+ style: { animationDelay: "150ms" }
6038
+ }
6039
+ ),
6040
+ /* @__PURE__ */ jsxRuntime.jsx(
6041
+ "span",
6042
+ {
6043
+ className: "boff-explore-dot",
6044
+ style: { animationDelay: "300ms" }
6045
+ }
6046
+ )
6047
+ ] }),
6048
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: activity || "Thinking\u2026" })
6049
+ ]
6050
+ }
6051
+ );
6052
+ }
6053
+ function UserBubble({ message }) {
6054
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
6055
+ "div",
6056
+ {
6057
+ "data-boff-explore": "user",
6058
+ className: "max-w-[85%] whitespace-pre-wrap break-words rounded-2xl rounded-br-md bg-primary px-4 py-2.5 text-sm leading-relaxed text-primary-foreground shadow-sm",
6059
+ children: message.content
6060
+ }
6061
+ ) });
6062
+ }
6063
+ function AssistantBubble({
6064
+ message,
6065
+ activity,
6066
+ onNavigate
6067
+ }) {
6068
+ const streaming = message.status === "PENDING" || message.status === "RUNNING";
6069
+ const body = message.content || message.partialContent || "";
6070
+ const failed = message.status === "FAILED";
6071
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6072
+ "div",
6073
+ {
6074
+ "data-boff-explore": "assistant",
6075
+ "data-status": message.status,
6076
+ className: "flex gap-3",
6077
+ children: [
6078
+ /* @__PURE__ */ jsxRuntime.jsx(
6079
+ "span",
6080
+ {
6081
+ "aria-hidden": "true",
6082
+ className: "mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary",
6083
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-4 w-4" })
6084
+ }
6085
+ ),
6086
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
6087
+ body ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl rounded-tl-md border border-border bg-card px-4 py-3 shadow-sm", children: [
6088
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "boff-prose boff-prose-sm text-card-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: body }) }),
6089
+ streaming ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "boff-explore-caret", "aria-hidden": "true" }) : null
6090
+ ] }) : null,
6091
+ streaming ? /* @__PURE__ */ jsxRuntime.jsx(ThinkingIndicator, { activity }) : null,
6092
+ failed && !body ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-2xl rounded-tl-md border border-border bg-muted px-4 py-3 text-sm text-muted-foreground", children: "That answer didn't come through. Try asking again." }) : null,
6093
+ message.references.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
6094
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Sources" }),
6095
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: message.references.map((reference) => /* @__PURE__ */ jsxRuntime.jsx(ReferenceCard, { reference }, reference.url)) })
6096
+ ] }) : null,
6097
+ message.ctas.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 pt-0.5", children: message.ctas.map((cta) => /* @__PURE__ */ jsxRuntime.jsx(
6098
+ CtaButton,
6099
+ {
6100
+ cta,
6101
+ onNavigate
6102
+ },
6103
+ `${cta.kind}-${cta.url}`
6104
+ )) }) : null
6105
+ ] })
6106
+ ]
6107
+ }
6108
+ );
6109
+ }
6110
+ function ErrorBanner({
6111
+ error,
6112
+ onRetry,
6113
+ onReset
6114
+ }) {
6115
+ const showRetry = error.retryable;
6116
+ const showReset = error.code === "CONVERSATION_LIMIT";
6117
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6118
+ "div",
6119
+ {
6120
+ "data-boff-explore": "error",
6121
+ "data-error-kind": error.kind,
6122
+ role: "alert",
6123
+ className: "mb-3 flex items-start gap-3 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2.5",
6124
+ children: [
6125
+ /* @__PURE__ */ jsxRuntime.jsx(
6126
+ "span",
6127
+ {
6128
+ "aria-hidden": "true",
6129
+ className: "mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-destructive text-destructive-foreground",
6130
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TriangleAlert, { className: "h-3 w-3" })
6131
+ }
6132
+ ),
6133
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "flex-1 text-sm leading-relaxed text-foreground", children: error.message }),
6134
+ showRetry ? /* @__PURE__ */ jsxRuntime.jsx(
6135
+ Button,
6136
+ {
6137
+ "data-boff-explore": "retry",
6138
+ type: "button",
6139
+ size: "sm",
6140
+ variant: "outline",
6141
+ onClick: onRetry,
6142
+ children: "Retry"
6143
+ }
6144
+ ) : null,
6145
+ showReset ? /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: onReset, children: "New chat" }) : null
6146
+ ]
6147
+ }
6148
+ );
6149
+ }
6150
+ function WelcomeState({
6151
+ title,
6152
+ body,
6153
+ prompts,
6154
+ products,
6155
+ focusProduct,
6156
+ onPrompt,
6157
+ onFocus,
6158
+ disabled
6159
+ }) {
6160
+ const [showAllProducts, setShowAllProducts] = React.useState(false);
6161
+ const visibleProducts = showAllProducts ? products : products.slice(0, PRODUCT_CHIP_PREVIEW);
6162
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-3xl px-4 py-10 sm:py-14", children: [
6163
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
6164
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-3 py-1 text-xs font-medium text-muted-foreground", children: [
6165
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-3.5 w-3.5" }),
6166
+ "AI answers, grounded in our documentation"
6167
+ ] }),
6168
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mt-5 text-balance text-3xl font-bold tracking-tight sm:text-4xl", children: title }),
6169
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mx-auto mt-3 max-w-2xl text-pretty text-base leading-relaxed text-muted-foreground sm:text-lg", children: body })
6170
+ ] }),
6171
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-9 grid gap-3 sm:grid-cols-3", children: CAPABILITIES.map(({ icon: Icon, title: capTitle, body: capBody }) => /* @__PURE__ */ jsxRuntime.jsxs(
6172
+ "li",
6173
+ {
6174
+ className: "rounded-xl border border-border bg-card p-4 text-left",
6175
+ children: [
6176
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-2.5 flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4" }) }),
6177
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-card-foreground", children: capTitle }),
6178
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs leading-relaxed text-muted-foreground", children: capBody })
6179
+ ]
6180
+ },
6181
+ capTitle
6182
+ )) }),
6183
+ prompts.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-9", children: [
6184
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Try asking" }),
6185
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: prompts.map((prompt) => /* @__PURE__ */ jsxRuntime.jsxs(
6186
+ "button",
6187
+ {
6188
+ "data-boff-explore": "chip",
6189
+ "data-chip-kind": "prompt",
6190
+ type: "button",
6191
+ disabled,
6192
+ onClick: () => onPrompt(prompt),
6193
+ className: "group inline-flex max-w-full items-center gap-1.5 rounded-full border border-border bg-card px-3.5 py-1.5 text-left text-sm text-card-foreground transition-colors hover:border-primary/40 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6194
+ children: [
6195
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: prompt }),
6196
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-3 w-3 shrink-0 rotate-45 text-muted-foreground transition-colors group-hover:text-primary" })
6197
+ ]
6198
+ },
6199
+ prompt
6200
+ )) })
6201
+ ] }) : null,
6202
+ products.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8", children: [
6203
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-2.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Focus on a product" }),
6204
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
6205
+ /* @__PURE__ */ jsxRuntime.jsx(
6206
+ "button",
6207
+ {
6208
+ "data-boff-explore": "chip",
6209
+ "data-chip-kind": "product",
6210
+ "data-product": "all",
6211
+ type: "button",
6212
+ "aria-pressed": focusProduct === null,
6213
+ onClick: () => onFocus(null),
6214
+ className: cn(
6215
+ "rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6216
+ focusProduct === null ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
6217
+ ),
6218
+ children: "All products"
6219
+ }
6220
+ ),
6221
+ visibleProducts.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
6222
+ "button",
6223
+ {
6224
+ "data-boff-explore": "chip",
6225
+ "data-chip-kind": "product",
6226
+ "data-product": product.slug,
6227
+ type: "button",
6228
+ "aria-pressed": focusProduct === product.slug,
6229
+ title: product.tagline ?? product.name,
6230
+ onClick: () => onFocus(focusProduct === product.slug ? null : product.slug),
6231
+ className: cn(
6232
+ "rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6233
+ focusProduct === product.slug ? "border-primary bg-primary text-primary-foreground" : "border-border bg-card text-muted-foreground hover:bg-accent hover:text-accent-foreground"
6234
+ ),
6235
+ children: product.name
6236
+ },
6237
+ product.slug
6238
+ )),
6239
+ products.length > PRODUCT_CHIP_PREVIEW ? /* @__PURE__ */ jsxRuntime.jsx(
6240
+ "button",
6241
+ {
6242
+ type: "button",
6243
+ onClick: () => setShowAllProducts((v) => !v),
6244
+ className: "rounded-full px-3 py-1 text-xs font-medium text-primary underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
6245
+ children: showAllProducts ? "Show fewer" : `Show all ${products.length}`
6246
+ }
6247
+ ) : null
6248
+ ] })
6249
+ ] }) : null
6250
+ ] });
6251
+ }
6252
+ function ExplorePage({
6253
+ seo,
6254
+ productSlug,
6255
+ examplePrompts,
6256
+ className,
6257
+ heightMode = "viewport",
6258
+ viewportOffset,
6259
+ onNavigate,
6260
+ onSend,
6261
+ welcomeTitle,
6262
+ welcomeBody
6263
+ }) {
6264
+ const pageUrl = React.useMemo(
6265
+ () => typeof window === "undefined" ? void 0 : window.location.href,
6266
+ []
6267
+ );
6268
+ const {
6269
+ phase,
6270
+ catalog,
6271
+ messages,
6272
+ activity,
6273
+ remainingToday,
6274
+ error,
6275
+ focusProduct,
6276
+ setFocusProduct,
6277
+ send,
6278
+ stop,
6279
+ retry,
6280
+ reset,
6281
+ canSend
6282
+ } = useExploreChat({ productSlug, pageUrl, onSend, examplePrompts });
6283
+ const [draft, setDraft] = React.useState("");
6284
+ const textareaRef = React.useRef(null);
6285
+ const scrollRef = React.useRef(null);
6286
+ const stickToBottomRef = React.useRef(true);
6287
+ const composingRef = React.useRef(false);
6288
+ const busy = phase === "sending" || phase === "streaming";
6289
+ const showWelcome = messages.length === 0;
6290
+ const maxLength = catalog.limits.maxMessageLength;
6291
+ const overLimit = draft.length > maxLength;
6292
+ const canSubmit = canSend && draft.trim().length > 0 && !overLimit && !busy;
6293
+ const resolvedTitle = welcomeTitle ?? catalog.welcomeTitle;
6294
+ const resolvedBody = welcomeBody ?? catalog.welcomeBody;
6295
+ const prompts = catalog.examplePrompts;
6296
+ const focusedProductName = React.useMemo(
6297
+ () => catalog.products.find((p) => p.slug === focusProduct)?.name ?? null,
6298
+ [catalog.products, focusProduct]
6299
+ );
6300
+ React.useEffect(() => {
6301
+ const el = textareaRef.current;
6302
+ if (!el) return;
6303
+ el.style.height = "auto";
6304
+ el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
6305
+ }, [draft]);
6306
+ const handleScroll = React.useCallback(() => {
6307
+ const el = scrollRef.current;
6308
+ if (!el) return;
6309
+ stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
6310
+ }, []);
6311
+ React.useEffect(() => {
6312
+ if (!stickToBottomRef.current) return;
6313
+ const el = scrollRef.current;
6314
+ if (!el) return;
6315
+ el.scrollTop = el.scrollHeight;
6316
+ }, [messages, activity]);
6317
+ const submitDraft = React.useCallback(() => {
6318
+ const text = draft.trim();
6319
+ if (!text || overLimit || busy || !canSend) return;
6320
+ setDraft("");
6321
+ stickToBottomRef.current = true;
6322
+ void send(text);
6323
+ textareaRef.current?.focus();
6324
+ }, [busy, canSend, draft, overLimit, send]);
6325
+ const sendPrompt = React.useCallback(
6326
+ (prompt) => {
6327
+ if (!canSend || busy) return;
6328
+ setDraft("");
6329
+ stickToBottomRef.current = true;
6330
+ void send(prompt);
6331
+ },
6332
+ [busy, canSend, send]
6333
+ );
6334
+ const handleKeyDown = React.useCallback(
6335
+ (event) => {
6336
+ if (event.key !== "Enter" || event.shiftKey) return;
6337
+ if (composingRef.current || event.nativeEvent.isComposing) return;
6338
+ event.preventDefault();
6339
+ submitDraft();
6340
+ },
6341
+ [submitDraft]
6342
+ );
6343
+ const composerDisabled = phase === "limited" || phase === "disabled" || !catalog.enabled;
6344
+ const captchaOn = Boolean(
6345
+ catalog.captchaRequired && catalog.recaptchaSiteKey
6346
+ );
6347
+ const rootStyle = heightMode === "viewport" ? { height: `calc(100dvh - ${normalizeOffset(viewportOffset)})` } : void 0;
6348
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6349
+ "div",
6350
+ {
6351
+ "data-boff-explore": "page",
6352
+ "data-phase": phase,
6353
+ className: cn(
6354
+ "flex w-full flex-col bg-background text-foreground",
6355
+ heightMode === "auto" && "min-h-[70vh]",
6356
+ className
6357
+ ),
6358
+ style: rootStyle,
6359
+ children: [
6360
+ seo ? /* @__PURE__ */ jsxRuntime.jsx(
6361
+ PageHead,
6362
+ {
6363
+ title: seo.title ?? "Explore \u2014 ask anything about our products",
6364
+ description: seo.description ?? "Ask questions in plain English and get answers drawn from our product documentation, with links to the exact pages they came from.",
6365
+ keywords: seo.keywords,
6366
+ image: seo.image,
6367
+ url: seo.url
6368
+ }
6369
+ ) : null,
6370
+ /* @__PURE__ */ jsxRuntime.jsx(ProseStyles, {}),
6371
+ /* @__PURE__ */ jsxRuntime.jsx(ExploreStyles, {}),
6372
+ !showWelcome ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 border-b border-border px-4 py-2.5", children: [
6373
+ /* @__PURE__ */ jsxRuntime.jsx(
6374
+ "span",
6375
+ {
6376
+ "aria-hidden": "true",
6377
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary",
6378
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-4 w-4" })
6379
+ }
6380
+ ),
6381
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "truncate text-sm font-semibold", children: resolvedTitle }),
6382
+ focusedProductName ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden shrink-0 rounded-full bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground sm:inline", children: focusedProductName }) : null,
6383
+ /* @__PURE__ */ jsxRuntime.jsxs(
6384
+ Button,
6385
+ {
6386
+ "data-boff-explore": "new-chat",
6387
+ type: "button",
6388
+ size: "sm",
6389
+ variant: "ghost",
6390
+ className: "ml-auto shrink-0 text-muted-foreground",
6391
+ onClick: reset,
6392
+ children: [
6393
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { className: "h-3.5 w-3.5" }),
6394
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "New chat" })
6395
+ ]
6396
+ }
6397
+ )
6398
+ ] }) : null,
6399
+ /* @__PURE__ */ jsxRuntime.jsx(
6400
+ "div",
6401
+ {
6402
+ ref: scrollRef,
6403
+ onScroll: handleScroll,
6404
+ "data-boff-explore": "messages",
6405
+ role: "log",
6406
+ "aria-live": "polite",
6407
+ "aria-label": "Explore conversation",
6408
+ className: "boff-explore-scroll min-h-0 flex-1 overflow-y-auto",
6409
+ children: showWelcome ? /* @__PURE__ */ jsxRuntime.jsx(
6410
+ WelcomeState,
6411
+ {
6412
+ title: resolvedTitle,
6413
+ body: resolvedBody,
6414
+ prompts,
6415
+ products: catalog.products,
6416
+ focusProduct,
6417
+ onPrompt: sendPrompt,
6418
+ onFocus: setFocusProduct,
6419
+ disabled: !canSend || busy
6420
+ }
6421
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-3xl space-y-6 px-4 py-6", children: messages.map(
6422
+ (message) => message.role === "USER" ? /* @__PURE__ */ jsxRuntime.jsx(UserBubble, { message }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(
6423
+ AssistantBubble,
6424
+ {
6425
+ message,
6426
+ activity,
6427
+ onNavigate
6428
+ },
6429
+ message.id
6430
+ )
6431
+ ) })
6432
+ }
6433
+ ),
6434
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border bg-background px-4 pb-4 pt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
6435
+ "form",
6436
+ {
6437
+ "data-boff-explore": "composer",
6438
+ className: "mx-auto w-full max-w-3xl",
6439
+ onSubmit: (event) => {
6440
+ event.preventDefault();
6441
+ submitDraft();
6442
+ },
6443
+ children: [
6444
+ error ? /* @__PURE__ */ jsxRuntime.jsx(ErrorBanner, { error, onRetry: retry, onReset: reset }) : null,
6445
+ /* @__PURE__ */ jsxRuntime.jsxs(
6446
+ "div",
6447
+ {
6448
+ className: cn(
6449
+ "flex items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-colors",
6450
+ overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
6451
+ ),
6452
+ children: [
6453
+ /* @__PURE__ */ jsxRuntime.jsx(
6454
+ Textarea,
6455
+ {
6456
+ ref: textareaRef,
6457
+ value: draft,
6458
+ rows: 1,
6459
+ disabled: composerDisabled,
6460
+ onChange: (event) => setDraft(event.target.value),
6461
+ onKeyDown: handleKeyDown,
6462
+ onCompositionStart: () => {
6463
+ composingRef.current = true;
6464
+ },
6465
+ onCompositionEnd: () => {
6466
+ composingRef.current = false;
6467
+ },
6468
+ "aria-label": "Ask a question",
6469
+ placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? `Ask about ${focusedProductName}\u2026` : "Ask anything about our products\u2026",
6470
+ className: "max-h-[200px] min-h-[44px] flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-sm shadow-none focus-visible:ring-0 md:text-sm"
6471
+ }
6472
+ ),
6473
+ busy ? /* @__PURE__ */ jsxRuntime.jsx(
6474
+ Button,
6475
+ {
6476
+ "data-boff-explore": "stop",
6477
+ type: "button",
6478
+ size: "icon",
6479
+ variant: "secondary",
6480
+ "aria-label": "Stop generating",
6481
+ onClick: stop,
6482
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { className: "h-4 w-4" })
6483
+ }
6484
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
6485
+ Button,
6486
+ {
6487
+ "data-boff-explore": "send",
6488
+ type: "submit",
6489
+ size: "icon",
6490
+ disabled: !canSubmit,
6491
+ "aria-label": "Send message",
6492
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-4 w-4" })
6493
+ }
6494
+ )
6495
+ ]
6496
+ }
6497
+ ),
6498
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
6499
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
6500
+ remainingToday !== null ? /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-boff-explore": "remaining", children: [
6501
+ remainingToday,
6502
+ " message",
6503
+ remainingToday === 1 ? "" : "s",
6504
+ " left today"
6505
+ ] }) : null,
6506
+ /* @__PURE__ */ jsxRuntime.jsxs(
6507
+ "span",
6508
+ {
6509
+ className: cn(
6510
+ "ml-auto tabular-nums",
6511
+ overLimit && "font-medium text-destructive"
6512
+ ),
6513
+ children: [
6514
+ draft.length.toLocaleString(),
6515
+ " / ",
6516
+ maxLength.toLocaleString()
6517
+ ]
6518
+ }
6519
+ )
6520
+ ] }),
6521
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-2 text-xs leading-relaxed text-muted-foreground", children: [
6522
+ "Answers are generated from our public documentation and can be imperfect \u2014 check anything important against the linked sources.",
6523
+ captchaOn ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6524
+ " ",
6525
+ "This site is protected by reCAPTCHA; the Google",
6526
+ " ",
6527
+ /* @__PURE__ */ jsxRuntime.jsx(
6528
+ "a",
6529
+ {
6530
+ href: "https://policies.google.com/privacy",
6531
+ target: "_blank",
6532
+ rel: "noopener noreferrer",
6533
+ className: "underline underline-offset-2 hover:text-foreground",
6534
+ children: "Privacy Policy"
6535
+ }
6536
+ ),
6537
+ " ",
6538
+ "and",
6539
+ " ",
6540
+ /* @__PURE__ */ jsxRuntime.jsx(
6541
+ "a",
6542
+ {
6543
+ href: "https://policies.google.com/terms",
6544
+ target: "_blank",
6545
+ rel: "noopener noreferrer",
6546
+ className: "underline underline-offset-2 hover:text-foreground",
6547
+ children: "Terms of Service"
6548
+ }
6549
+ ),
6550
+ " ",
6551
+ "apply."
6552
+ ] }) : null
6553
+ ] })
6554
+ ]
6555
+ }
6556
+ ) })
6557
+ ]
6558
+ }
6559
+ );
6560
+ }
6561
+ function useCurrentPath(explicit) {
6562
+ const [path, setPath] = React.useState(explicit ?? null);
6563
+ React.useEffect(() => {
6564
+ if (explicit !== void 0) {
6565
+ setPath(explicit);
6566
+ return;
6567
+ }
6568
+ if (typeof window === "undefined") return;
6569
+ const read = () => setPath(window.location.pathname);
6570
+ read();
6571
+ window.addEventListener("popstate", read);
6572
+ return () => window.removeEventListener("popstate", read);
6573
+ }, [explicit]);
6574
+ return path;
6575
+ }
6576
+ function samePath(a, b) {
6577
+ if (!a) return false;
6578
+ const norm = (v) => v.length > 1 ? v.replace(/\/+$/, "") : v;
6579
+ return norm(a) === norm(b);
6580
+ }
6581
+ function ExploreCta({
6582
+ variant,
6583
+ href = "/explore",
6584
+ label = "Ask AI",
6585
+ className,
6586
+ onNavigate,
6587
+ onClick,
6588
+ currentPath
6589
+ }) {
6590
+ const path = useCurrentPath(currentPath);
6591
+ const isActive = samePath(path, href);
6592
+ if (variant === "floating" && isActive) return null;
6593
+ const handleClick = (event) => {
6594
+ onClick?.();
6595
+ if (!onNavigate) return;
6596
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) {
6597
+ return;
6598
+ }
6599
+ event.preventDefault();
6600
+ onNavigate(href);
6601
+ };
6602
+ const classes = {
6603
+ header: "inline-flex items-center gap-1.5 rounded-full bg-gradient-to-r from-primary to-primary/70 px-3.5 py-1.5 text-sm font-medium text-primary-foreground shadow-sm transition-opacity hover:opacity-90",
6604
+ mobile: "inline-flex w-full items-center gap-2 rounded-lg border border-border bg-card px-3 py-2 text-sm font-medium text-card-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
6605
+ floating: "fixed bottom-5 right-5 z-40 inline-flex items-center gap-2 rounded-full bg-gradient-to-r from-primary to-primary/70 px-4 py-3 text-sm font-medium text-primary-foreground shadow-lg transition-opacity hover:opacity-90"
6606
+ };
6607
+ const hooks = {
6608
+ header: "cta-header",
6609
+ mobile: "cta-mobile",
6610
+ floating: "cta-floating"
6611
+ };
6612
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6613
+ "a",
6614
+ {
6615
+ "data-boff-explore": hooks[variant],
6616
+ href,
6617
+ onClick: handleClick,
6618
+ "aria-label": label,
6619
+ "aria-current": isActive ? "page" : void 0,
6620
+ className: cn(
6621
+ classes[variant],
6622
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
6623
+ className
6624
+ ),
6625
+ children: [
6626
+ /* @__PURE__ */ jsxRuntime.jsx(
6627
+ lucideReact.Sparkles,
6628
+ {
6629
+ className: cn(
6630
+ "shrink-0",
6631
+ variant === "floating" ? "h-4 w-4" : "h-3.5 w-3.5"
6632
+ ),
6633
+ "aria-hidden": "true"
6634
+ }
6635
+ ),
6636
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(variant === "floating" && "hidden sm:inline"), children: label })
6637
+ ]
6638
+ }
6639
+ );
6640
+ }
6641
+ function RybbitAnalytics({
6642
+ siteId,
6643
+ scriptUrl = "https://analytics.burdenoff.com/api/script.js",
6644
+ enabled = true
6645
+ }) {
6646
+ React.useEffect(() => {
6647
+ if (!enabled || !siteId) return;
6648
+ const existingScript = document.querySelector(
6649
+ `script[data-site-id="${siteId}"]`
6650
+ );
6651
+ if (existingScript) return;
6652
+ const script = document.createElement("script");
6653
+ script.src = scriptUrl;
6654
+ script.defer = true;
6655
+ script.crossOrigin = "anonymous";
6656
+ script.setAttribute("data-site-id", siteId);
6657
+ document.head.appendChild(script);
6658
+ return () => {
6659
+ const scriptToRemove = document.querySelector(
6660
+ `script[data-site-id="${siteId}"]`
6661
+ );
6662
+ if (scriptToRemove) {
6663
+ document.head.removeChild(scriptToRemove);
6664
+ }
6665
+ };
6666
+ }, [siteId, scriptUrl, enabled]);
6667
+ return null;
6668
+ }
6669
+ var PUBLIC_JOB_POSTING_FIELDS = (
6670
+ /* GraphQL */
6671
+ `
6672
+ id
6673
+ slug
6674
+ title
6675
+ department
6676
+ location
6677
+ employmentType
6678
+ openings
6679
+ description
6680
+ overview
6681
+ responsibilities
6682
+ learnings
6683
+ qualifications
6684
+ rounds {
6685
+ roundNumber
6686
+ name
6687
+ roundType
6688
+ isRequired
6689
+ description
6690
+ }
6691
+ `
6692
+ );
6693
+ var PUBLIC_JOB_POSTINGS_QUERY = (
6694
+ /* GraphQL */
6695
+ `
6696
+ query PublicJobPostings($productId: ID, $limit: Int, $offset: Int) {
6697
+ publicJobPostings(productId: $productId, limit: $limit, offset: $offset) {
6698
+ ${PUBLIC_JOB_POSTING_FIELDS}
6699
+ }
6700
+ }
6701
+ `
6702
+ );
6703
+ var PUBLIC_JOB_POSTING_QUERY = (
6704
+ /* GraphQL */
6705
+ `
6706
+ query PublicJobPosting($slug: String!, $productId: ID) {
6707
+ publicJobPosting(slug: $slug, productId: $productId) {
6708
+ ${PUBLIC_JOB_POSTING_FIELDS}
6709
+ }
6710
+ }
6711
+ `
6712
+ );
6713
+ var PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY = (
6714
+ /* GraphQL */
6715
+ `
6716
+ query PublicJobPostingsForSubmission(
6717
+ $productId: ID
6718
+ $limit: Int
6719
+ $offset: Int
6720
+ ) {
6721
+ publicJobPostingsForSubmission(
6722
+ productId: $productId
6723
+ limit: $limit
6724
+ offset: $offset
6725
+ ) {
6726
+ id
6727
+ title
6728
+ }
6729
+ }
6730
+ `
6731
+ );
6732
+ async function fetchPublicJobPostings(client, options) {
6733
+ const result = await client.query(PUBLIC_JOB_POSTINGS_QUERY, {
6734
+ productId: options?.productId,
6735
+ limit: options?.limit ?? 50,
6736
+ offset: options?.offset ?? 0
6737
+ });
6738
+ if (result.errors?.length) {
6739
+ throw new Error(result.errors[0]?.message ?? "Unknown error");
6740
+ }
6741
+ return result.data?.publicJobPostings ?? [];
6742
+ }
6743
+ async function fetchPublicJobPosting(client, slug, options) {
6744
+ const result = await client.query(PUBLIC_JOB_POSTING_QUERY, {
6745
+ slug,
6746
+ productId: options?.productId
6747
+ });
6748
+ if (result.errors?.length) {
6749
+ throw new Error(result.errors[0]?.message ?? "Unknown error");
6750
+ }
6751
+ return result.data?.publicJobPosting ?? null;
6752
+ }
6753
+ async function fetchPublicJobPostingsForSubmission(client, options) {
6754
+ const result = await client.query(PUBLIC_JOB_POSTINGS_FOR_SUBMISSION_QUERY, {
6755
+ productId: options?.productId,
6756
+ limit: options?.limit ?? 50,
6757
+ offset: options?.offset ?? 0
6758
+ });
6759
+ if (result.errors?.length) {
6760
+ throw new Error(result.errors[0]?.message ?? "Unknown error");
6761
+ }
6762
+ return result.data?.publicJobPostingsForSubmission ?? [];
6763
+ }
6764
+ function usePublicJobPostings(options) {
6765
+ const client = useWebSDK();
6766
+ const config = useWebSDKConfig();
6767
+ const { product } = useProduct();
6768
+ const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
6769
+ const [postings, setPostings] = React.useState([]);
6770
+ const [loading, setLoading] = React.useState(true);
6771
+ const [error, setError] = React.useState(null);
6772
+ const fetchPostings = React.useCallback(async () => {
6773
+ setLoading(true);
6774
+ setError(null);
6775
+ try {
6776
+ const items = await fetchPublicJobPostings(client, {
6777
+ productId: resolvedProductId,
6778
+ limit: options?.limit,
6779
+ offset: options?.offset
6780
+ });
6781
+ setPostings(items);
6782
+ } catch (err) {
6783
+ setError(
6784
+ err instanceof Error ? err.message : "Failed to fetch job postings"
6785
+ );
6786
+ setPostings([]);
6787
+ } finally {
6788
+ setLoading(false);
6789
+ }
6790
+ }, [client, resolvedProductId, options?.limit, options?.offset]);
6791
+ React.useEffect(() => {
6792
+ fetchPostings();
6793
+ }, [fetchPostings]);
6794
+ return { postings, loading, error, refetch: fetchPostings };
6795
+ }
6796
+ function usePublicJobPostingsForSubmission(options) {
6797
+ const client = useWebSDK();
6798
+ const config = useWebSDKConfig();
6799
+ const { product } = useProduct();
6800
+ const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
6801
+ const [postings, setPostings] = React.useState([]);
6802
+ const [loading, setLoading] = React.useState(true);
6803
+ const [error, setError] = React.useState(null);
6804
+ const fetchPostings = React.useCallback(async () => {
6805
+ setLoading(true);
6806
+ setError(null);
6807
+ try {
6808
+ const items = await fetchPublicJobPostingsForSubmission(client, {
6809
+ productId: resolvedProductId,
6810
+ limit: options?.limit,
6811
+ offset: options?.offset
6812
+ });
6813
+ setPostings(items);
6814
+ } catch (err) {
6815
+ setError(
6816
+ err instanceof Error ? err.message : "Failed to fetch job postings"
6817
+ );
6818
+ setPostings([]);
6819
+ } finally {
6820
+ setLoading(false);
6821
+ }
6822
+ }, [client, resolvedProductId, options?.limit, options?.offset]);
6823
+ React.useEffect(() => {
6824
+ fetchPostings();
6825
+ }, [fetchPostings]);
6826
+ return { postings, loading, error, refetch: fetchPostings };
6827
+ }
6828
+ function usePublicJobPosting(slug, options) {
6829
+ const client = useWebSDK();
6830
+ const config = useWebSDKConfig();
6831
+ const { product } = useProduct();
6832
+ const resolvedProductId = options?.productId ?? product?.id ?? config.productId;
6833
+ const [posting, setPosting] = React.useState(null);
6834
+ const [loading, setLoading] = React.useState(true);
6835
+ const [error, setError] = React.useState(null);
6836
+ const fetchPosting = React.useCallback(async () => {
6837
+ if (!slug) {
6838
+ setPosting(null);
6839
+ setLoading(false);
6840
+ return;
6841
+ }
6842
+ setLoading(true);
6843
+ setError(null);
6844
+ try {
6845
+ const item = await fetchPublicJobPosting(client, slug, {
6846
+ productId: resolvedProductId
6847
+ });
6848
+ setPosting(item);
6849
+ } catch (err) {
6850
+ setError(
6851
+ err instanceof Error ? err.message : "Failed to fetch job posting"
6852
+ );
6853
+ setPosting(null);
6854
+ } finally {
6855
+ setLoading(false);
6856
+ }
6857
+ }, [client, resolvedProductId, slug]);
6858
+ React.useEffect(() => {
6859
+ fetchPosting();
6860
+ }, [fetchPosting]);
6861
+ return { posting, loading, error, refetch: fetchPosting };
6862
+ }
6863
+ var BLOG_POSTS_QUERY = `
6864
+ query BlogPosts($productId: String!, $limit: Int, $offset: Int) {
6865
+ publicContentPages(productId: $productId, limit: $limit, offset: $offset) {
6866
+ items {
6867
+ id
6868
+ title
6869
+ slug
6870
+ lastUpdated
6871
+ tags
6872
+ seoDescription
6873
+ excerpt
6874
+ thumbnail
6875
+ author
6876
+ publishedAt
6877
+ readingTime
6878
+ featured
6879
+ category
6880
+ }
6881
+ total
6882
+ hasMore
6883
+ }
6884
+ }
6885
+ `;
6886
+ var BLOG_POST_QUERY = `
6887
+ query BlogPost($slug: String!, $productId: String) {
6888
+ publicContentPage(slug: $slug, productId: $productId) {
5165
6889
  id
5166
6890
  title
5167
6891
  slug
@@ -9479,181 +11203,6 @@ function StoreProductDetailPage({
9479
11203
  ] });
9480
11204
  }
9481
11205
 
9482
- // src/data/products.ts
9483
- var ECOSYSTEM_PRODUCTS = [
9484
- {
9485
- slug: "workspaces",
9486
- name: "Workspaces",
9487
- websiteUrl: "https://burdenoff.com"
9488
- },
9489
- {
9490
- slug: "vibecontrols",
9491
- name: "VibeControls",
9492
- websiteUrl: "https://vibecontrols.com"
9493
- },
9494
- {
9495
- slug: "fluidgrids",
9496
- name: "FluidGrids",
9497
- websiteUrl: "https://fluidgrids.com"
9498
- },
9499
- // Botlit ships on botlit.ai — botlit.com does not resolve.
9500
- { slug: "botlit", name: "Botlit", websiteUrl: "https://www.botlit.ai" },
9501
- {
9502
- slug: "bigconsole",
9503
- name: "BigConsole",
9504
- websiteUrl: "https://bigconsole.com"
9505
- },
9506
- {
9507
- slug: "semanticfed",
9508
- name: "SemanticFed",
9509
- websiteUrl: "https://semanticfed.com"
9510
- },
9511
- {
9512
- slug: "intelwatchtower",
9513
- name: "IntelWatchtower",
9514
- websiteUrl: "https://intelwatchtower.com"
9515
- },
9516
- { slug: "buildmyiq", name: "BuildMyIQ", websiteUrl: "https://buildmyiq.com" },
9517
- {
9518
- slug: "assethandler",
9519
- name: "AssetHandler",
9520
- websiteUrl: "https://assethandler.com"
9521
- },
9522
- {
9523
- slug: "crewfoundry",
9524
- name: "CrewFoundry",
9525
- websiteUrl: "https://crewfoundry.com"
9526
- },
9527
- {
9528
- slug: "planmagnet",
9529
- name: "PlanMagnet",
9530
- websiteUrl: "https://planmagnet.com"
9531
- },
9532
- {
9533
- slug: "eventfullymanaged",
9534
- name: "EventfullyManaged",
9535
- websiteUrl: "https://eventfullymanaged.com"
9536
- },
9537
- {
9538
- slug: "headshotmarketing",
9539
- name: "HeadshotMarketing",
9540
- websiteUrl: "https://headshotmarketing.com"
9541
- },
9542
- {
9543
- slug: "technospam",
9544
- name: "TechnoSpam",
9545
- websiteUrl: "https://technospam.com"
9546
- },
9547
- { slug: "kadaikodi", name: "Kadaikodi", websiteUrl: "https://kadaikodi.com" },
9548
- {
9549
- slug: "movethewheels",
9550
- name: "MoveTheWheels",
9551
- websiteUrl: "https://movethewheels.com"
9552
- },
9553
- {
9554
- slug: "healthybowl",
9555
- name: "Healthy Bowl",
9556
- websiteUrl: "https://gethealthybowl.com"
9557
- },
9558
- { slug: "ggnomad", name: "GGNomad", websiteUrl: "https://ggnomad.com" },
9559
- {
9560
- slug: "timecampus",
9561
- name: "TimeCampus",
9562
- websiteUrl: "https://timecampus.com"
9563
- },
9564
- {
9565
- slug: "manufacturedops",
9566
- name: "ManufacturedOps",
9567
- websiteUrl: "https://manufacturedops.com"
9568
- },
9569
- {
9570
- slug: "theglobalfuel",
9571
- name: "TheGlobalFuel",
9572
- websiteUrl: "https://theglobalfuel.com"
9573
- },
9574
- {
9575
- slug: "housingvista",
9576
- name: "HousingVista",
9577
- websiteUrl: "https://housingvista.com"
9578
- },
9579
- {
9580
- slug: "proserviceworld",
9581
- name: "ProServiceWorld",
9582
- websiteUrl: "https://proserviceworld.com"
9583
- },
9584
- {
9585
- slug: "mybodyshield",
9586
- name: "MyBodyShield",
9587
- websiteUrl: "https://mybodyshield.com"
9588
- },
9589
- {
9590
- slug: "artistrybase",
9591
- name: "ArtistryBase",
9592
- websiteUrl: "https://artistrybase.com"
9593
- },
9594
- {
9595
- slug: "theculturepost",
9596
- name: "TheCulturePost",
9597
- websiteUrl: "https://theculturepost.com"
9598
- },
9599
- { slug: "collabkin", name: "CollabKin", websiteUrl: "https://collabkin.com" },
9600
- {
9601
- slug: "comradecircle",
9602
- name: "ComradeCircle",
9603
- websiteUrl: "https://comradecircle.com"
9604
- },
9605
- {
9606
- slug: "brainyrich",
9607
- name: "BrainyRich",
9608
- websiteUrl: "https://brainyrich.com"
9609
- },
9610
- {
9611
- slug: "cosmicintersection",
9612
- name: "CosmicIntersection",
9613
- websiteUrl: "https://cosmicintersection.com"
9614
- },
9615
- {
9616
- slug: "govcitizenhub",
9617
- name: "GovCitizenHub",
9618
- websiteUrl: "https://govcitizenhub.com"
9619
- },
9620
- {
9621
- slug: "adaptercloud",
9622
- name: "AdapterCloud",
9623
- websiteUrl: "https://adaptercloud.com"
9624
- },
9625
- {
9626
- slug: "subscriberbot",
9627
- name: "Subscriber Bot",
9628
- websiteUrl: "https://subscriberbot.com"
9629
- },
9630
- {
9631
- slug: "hookmovies",
9632
- name: "HookMovies",
9633
- websiteUrl: "https://hookmovies.com"
9634
- },
9635
- {
9636
- slug: "coolandlovely",
9637
- name: "CoolAndLovely",
9638
- websiteUrl: "https://coolandlovely.com"
9639
- },
9640
- {
9641
- slug: "atheletebridge",
9642
- name: "AthleteBridge",
9643
- websiteUrl: "https://atheletebridge.com"
9644
- },
9645
- {
9646
- slug: "labsofscience",
9647
- name: "LabsOfScience",
9648
- websiteUrl: "https://labsofscience.com"
9649
- },
9650
- {
9651
- slug: "ecoimpacthub",
9652
- name: "EcoImpactHub",
9653
- websiteUrl: "https://ecoimpacthub.com"
9654
- }
9655
- ];
9656
-
9657
11206
  exports.BlogListPage = BlogListPage;
9658
11207
  exports.BlogPostPage = BlogPostPage;
9659
11208
  exports.Button = Button;
@@ -9666,8 +11215,15 @@ exports.ContactPage = ContactPage;
9666
11215
  exports.ContentRenderer = ContentRenderer;
9667
11216
  exports.ContractsPage = ContractsPage;
9668
11217
  exports.DEFAULT_CONTACT_CATEGORIES = DEFAULT_CONTACT_CATEGORIES;
11218
+ exports.DEFAULT_EXPLORE_EXAMPLE_PROMPTS = DEFAULT_EXPLORE_EXAMPLE_PROMPTS;
9669
11219
  exports.ECOSYSTEM_PRODUCTS = ECOSYSTEM_PRODUCTS;
11220
+ exports.EXPLORE_CATALOG_QUERY = EXPLORE_CATALOG_QUERY;
11221
+ exports.EXPLORE_MESSAGE_QUERY = EXPLORE_MESSAGE_QUERY;
11222
+ exports.EXPLORE_RECAPTCHA_ACTION = EXPLORE_RECAPTCHA_ACTION;
9670
11223
  exports.ElectronDownloadLink = ElectronDownloadLink;
11224
+ exports.ExploreCta = ExploreCta;
11225
+ exports.ExplorePage = ExplorePage;
11226
+ exports.FALLBACK_EXPLORE_LIMITS = FALLBACK_EXPLORE_LIMITS;
9671
11227
  exports.Input = Input;
9672
11228
  exports.Label = Label;
9673
11229
  exports.LaunchCountdown = LaunchCountdown;
@@ -9684,6 +11240,7 @@ exports.ProductCard = ProductCard;
9684
11240
  exports.ProductProvider = ProductProvider;
9685
11241
  exports.ResourceLinks = ResourceLinks;
9686
11242
  exports.RybbitAnalytics = RybbitAnalytics;
11243
+ exports.SEND_EXPLORE_MESSAGE_MUTATION = SEND_EXPLORE_MESSAGE_MUTATION;
9687
11244
  exports.Select = Select;
9688
11245
  exports.SocialLinks = SocialLinks;
9689
11246
  exports.StarRating = StarRating;
@@ -9699,6 +11256,7 @@ exports.WebSDKClient = WebSDKClient;
9699
11256
  exports.WebSDKProvider = WebSDKProvider;
9700
11257
  exports.WebsiteSwitcher = WebsiteSwitcher;
9701
11258
  exports.buttonVariants = buttonVariants;
11259
+ exports.classifyExploreError = classifyExploreError;
9702
11260
  exports.cn = cn;
9703
11261
  exports.detectElectronPlatform = detectElectronPlatform;
9704
11262
  exports.fetchPublicJobPosting = fetchPublicJobPosting;
@@ -9706,9 +11264,12 @@ exports.fetchPublicJobPostings = fetchPublicJobPostings;
9706
11264
  exports.fetchPublicJobPostingsForSubmission = fetchPublicJobPostingsForSubmission;
9707
11265
  exports.formatDownloads = formatDownloads;
9708
11266
  exports.formatPrice = formatPrice2;
11267
+ exports.getRecaptchaV3Token = getRecaptchaV3Token;
9709
11268
  exports.isValidUrl = isValidUrl;
11269
+ exports.loadRecaptchaV3 = loadRecaptchaV3;
9710
11270
  exports.useContentPage = useContentPage;
9711
11271
  exports.useContentPages = useContentPages;
11272
+ exports.useExploreChat = useExploreChat;
9712
11273
  exports.useProduct = useProduct;
9713
11274
  exports.useProductConfig = useProductConfig;
9714
11275
  exports.usePublicJobPosting = usePublicJobPosting;