useInkeepConfig.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import { useEffect, useState } from "react";
  2. import type {
  3. InkeepBaseSettings,
  4. InkeepAIChatSettings,
  5. InkeepSearchSettings,
  6. } from "@inkeep/cxkit-react";
  7. const baseSettings: InkeepBaseSettings = {
  8. apiKey: "815f8fee7a5da7d98c681626dfbd268bdf7f7dc7cb80f618",
  9. primaryBrandColor: "#9945ff",
  10. theme: {
  11. styles: [
  12. {
  13. key: "custom-theme",
  14. type: "style",
  15. value: `
  16. #inkeep-widget-root {
  17. font-size: 1rem;
  18. }
  19. .ikp-search-bar__container {
  20. margin: 0 0 0 16px;
  21. }
  22. .ikp-search-bar__button {
  23. padding: 0px 8px;
  24. }
  25. @media (min-width: 768px) {
  26. .ikp-search-bar__container {
  27. min-width: 0px;
  28. }
  29. }
  30. @media (max-width: 768px) {
  31. .ikp-search-bar__icon {
  32. font-size: 24px;
  33. color: var(--docsearch-text-color);
  34. }
  35. .ikp-search-bar__button {
  36. border-color: transparent;
  37. }
  38. .ikp-search-bar__text {
  39. display: none;
  40. }
  41. .ikp-search-bar__kbd-wrapper {
  42. display: none;
  43. }
  44. .search-bar__content-wrapper {
  45. gap: 0;
  46. }
  47. }
  48. `,
  49. },
  50. ],
  51. },
  52. transformSource: source => {
  53. const urlPatterns = {
  54. anchorDocs: "https://www.anchor-lang.com/docs",
  55. solanaDocs: "solana.com",
  56. stackExchange: "https://solana.stackexchange.com/",
  57. anzaDocs: "https://docs.anza.xyz/",
  58. github: "github.com",
  59. } as const;
  60. const tabConfig = {
  61. [urlPatterns.anchorDocs]: {
  62. tab: "Anchor Docs",
  63. icon: undefined,
  64. shouldOpenInNewTab: false,
  65. getBreadcrumbs: (crumbs: string[]) => crumbs,
  66. },
  67. [urlPatterns.solanaDocs]: {
  68. tab: "Solana Docs",
  69. icon: undefined,
  70. shouldOpenInNewTab: true,
  71. getBreadcrumbs: (crumbs: string[]) => ["Docs", ...crumbs.slice(1)],
  72. },
  73. [urlPatterns.anzaDocs]: {
  74. tab: "Anza Docs",
  75. icon: undefined,
  76. shouldOpenInNewTab: true,
  77. getBreadcrumbs: (crumbs: string[]) => crumbs,
  78. },
  79. [urlPatterns.stackExchange]: {
  80. tab: "Stack Exchange",
  81. icon: undefined,
  82. shouldOpenInNewTab: true,
  83. getBreadcrumbs: (crumbs: string[]) => crumbs,
  84. },
  85. [urlPatterns.github]: {
  86. tab: "GitHub",
  87. icon: "FaGithub",
  88. shouldOpenInNewTab: true,
  89. getBreadcrumbs: (crumbs: string[]) => crumbs,
  90. },
  91. } as const;
  92. // Find matching config based on URL
  93. const matchingPattern = Object.keys(tabConfig).find(pattern =>
  94. source.url.includes(pattern),
  95. );
  96. const config = matchingPattern
  97. ? tabConfig[matchingPattern as keyof typeof tabConfig]
  98. : null;
  99. if (!config) {
  100. return source;
  101. }
  102. const breadcrumbs = config.getBreadcrumbs(source.breadcrumbs);
  103. const existingTabs = source.tabs ?? [];
  104. // Check if tab already exists
  105. const tabExists = existingTabs.some(existingTab =>
  106. typeof existingTab === "string"
  107. ? existingTab === config.tab
  108. : Array.isArray(existingTab) && existingTab[0] === config.tab,
  109. );
  110. const tabs = tabExists
  111. ? existingTabs
  112. : [
  113. ...existingTabs,
  114. [
  115. config.tab,
  116. {
  117. breadcrumbs:
  118. breadcrumbs[0] === config.tab
  119. ? breadcrumbs.slice(1)
  120. : breadcrumbs,
  121. },
  122. ] as const,
  123. ];
  124. return {
  125. ...source,
  126. breadcrumbs,
  127. tabs,
  128. shouldOpenInNewTab: config.shouldOpenInNewTab,
  129. icon: config.icon ? { builtIn: config.icon } : undefined,
  130. };
  131. },
  132. };
  133. const searchSettings: InkeepSearchSettings = {
  134. placeholder: "Search",
  135. tabs: [
  136. "All",
  137. "Anchor Docs",
  138. "Solana Docs",
  139. "Anza Docs",
  140. "Stack Exchange",
  141. "GitHub",
  142. ],
  143. };
  144. const aiChatSettings: InkeepAIChatSettings = {
  145. chatSubjectName: "Anchor",
  146. introMessage:
  147. "I'm an AI assistant trained on documentation, github repos, and other content. Ask me anything about `Solana`.",
  148. aiAssistantAvatar: "https://solana.com/favicon.png",
  149. disclaimerSettings: {
  150. isEnabled: true,
  151. label: "",
  152. },
  153. toolbarButtonLabels: {
  154. getHelp: "Get Support",
  155. },
  156. getHelpOptions: [
  157. {
  158. name: "Discord",
  159. action: {
  160. type: "open_link",
  161. url: "https://discord.com/invite/NHHGSXAnXk",
  162. },
  163. icon: {
  164. builtIn: "FaDiscord",
  165. },
  166. },
  167. {
  168. name: "Stack Exchange",
  169. action: {
  170. type: "open_link",
  171. url: "https://solana.stackexchange.com/",
  172. },
  173. icon: {
  174. builtIn: "FaStackOverflow",
  175. },
  176. },
  177. ],
  178. exampleQuestions: [
  179. "How to quickly install Solana dependencies for local development?",
  180. "What is the Anchor Framework?",
  181. "How to build an Anchor Program?",
  182. ],
  183. };
  184. export function useInkeepConfig() {
  185. const [syncTarget, setSyncTarget] = useState<HTMLElement | null>(null);
  186. // We do this because document is not available in the server
  187. useEffect(() => {
  188. setSyncTarget(document.documentElement);
  189. }, []);
  190. return {
  191. baseSettings: {
  192. ...baseSettings,
  193. colorMode: {
  194. sync: {
  195. target: syncTarget,
  196. attributes: ["class"],
  197. isDarkMode: (attributes: { class: string | string[] }) =>
  198. !!attributes.class?.includes("dark"),
  199. },
  200. },
  201. },
  202. searchSettings,
  203. aiChatSettings,
  204. };
  205. }