瀏覽代碼

docs: Add Inkeep Searchbar (#3597)

John 7 月之前
父節點
當前提交
1053b717dc

+ 0 - 36
docs/app/components/inkeep-script.tsx

@@ -1,36 +0,0 @@
-"use client";
-declare global {
-  interface Window {
-    Inkeep: any;
-  }
-}
-
-import Script from "next/script";
-import { settings } from "../inkeep-settings";
-
-export function InkeepScript() {
-  return (
-    <Script
-      id="inkeep-script"
-      src="https://unpkg.com/@inkeep/uikit-js@0.3.14/dist/embed.js"
-      type="module"
-      strategy="afterInteractive"
-      onReady={() => {
-        const config = {
-          colorModeSync: {
-            observedElement: document.documentElement,
-            isDarkModeCallback: (el: any) => {
-              return el.classList.contains("dark");
-            },
-            colorModeAttribute: "class",
-          },
-          properties: settings,
-        };
-        window.Inkeep().embed({
-          componentType: "ChatButton",
-          ...config,
-        });
-      }}
-    />
-  );
-}

+ 12 - 0
docs/app/components/inkeep/inkeep-chat-button.tsx

@@ -0,0 +1,12 @@
+"use client";
+
+import {
+  InkeepChatButton as ChatButton,
+  InkeepChatButtonProps,
+} from "@inkeep/cxkit-react";
+import { useInkeepConfig } from "./useInkeepConfig";
+
+export function InkeepChatButton() {
+  const inkeepConfig = useInkeepConfig();
+  return <ChatButton {...(inkeepConfig as InkeepChatButtonProps)} />;
+}

+ 25 - 0
docs/app/components/inkeep/inkeep-search.tsx

@@ -0,0 +1,25 @@
+"use client";
+
+import type { SharedProps } from "fumadocs-ui/components/dialog/search";
+import {
+  InkeepModalSearchAndChat,
+  type InkeepModalSearchAndChatProps,
+} from "@inkeep/cxkit-react";
+import { useInkeepConfig } from "./useInkeepConfig";
+
+export default function CustomDialog(props: SharedProps) {
+  const baseConfig = useInkeepConfig();
+  const { open, onOpenChange } = props;
+
+  const config = {
+    ...baseConfig,
+    modalSettings: {
+      isOpen: open,
+      onOpenChange,
+    },
+  };
+
+  return (
+    <InkeepModalSearchAndChat {...(config as InkeepModalSearchAndChatProps)} />
+  );
+}

+ 217 - 0
docs/app/components/inkeep/useInkeepConfig.ts

@@ -0,0 +1,217 @@
+import { useEffect, useState } from "react";
+import type {
+  InkeepBaseSettings,
+  InkeepAIChatSettings,
+  InkeepSearchSettings,
+} from "@inkeep/cxkit-react";
+
+const baseSettings: InkeepBaseSettings = {
+  apiKey: "815f8fee7a5da7d98c681626dfbd268bdf7f7dc7cb80f618",
+  primaryBrandColor: "#9945ff",
+  theme: {
+    styles: [
+      {
+        key: "custom-theme",
+        type: "style",
+        value: `
+        #inkeep-widget-root {
+          font-size: 1rem; 
+        }
+        .ikp-search-bar__container {
+          margin: 0 0 0 16px;
+        }
+        .ikp-search-bar__button {
+          padding: 0px 8px;
+        }
+        @media (min-width: 768px) {
+          .ikp-search-bar__container {
+            min-width: 0px;
+          }
+        }
+        @media (max-width: 768px) {
+          .ikp-search-bar__icon {
+            font-size: 24px;
+            color: var(--docsearch-text-color);
+          }
+          .ikp-search-bar__button {
+            border-color: transparent;
+          }
+          .ikp-search-bar__text {
+            display: none;
+          }
+          .ikp-search-bar__kbd-wrapper {
+            display: none;
+          }
+          .search-bar__content-wrapper {
+            gap: 0;
+          }
+        }
+      `,
+      },
+    ],
+  },
+  transformSource: source => {
+    const urlPatterns = {
+      anchorDocs: "https://www.anchor-lang.com/docs",
+      solanaDocs: "solana.com",
+      stackExchange: "https://solana.stackexchange.com/",
+      anzaDocs: "https://docs.anza.xyz/",
+      github: "github.com",
+    } as const;
+
+    const tabConfig = {
+      [urlPatterns.anchorDocs]: {
+        tab: "Anchor Docs",
+        icon: undefined,
+        shouldOpenInNewTab: false,
+        getBreadcrumbs: (crumbs: string[]) => crumbs,
+      },
+      [urlPatterns.solanaDocs]: {
+        tab: "Solana Docs",
+        icon: undefined,
+        shouldOpenInNewTab: true,
+        getBreadcrumbs: (crumbs: string[]) => ["Docs", ...crumbs.slice(1)],
+      },
+      [urlPatterns.anzaDocs]: {
+        tab: "Anza Docs",
+        icon: undefined,
+        shouldOpenInNewTab: true,
+        getBreadcrumbs: (crumbs: string[]) => crumbs,
+      },
+      [urlPatterns.stackExchange]: {
+        tab: "Stack Exchange",
+        icon: undefined,
+        shouldOpenInNewTab: true,
+        getBreadcrumbs: (crumbs: string[]) => crumbs,
+      },
+      [urlPatterns.github]: {
+        tab: "GitHub",
+        icon: "FaGithub",
+        shouldOpenInNewTab: true,
+        getBreadcrumbs: (crumbs: string[]) => crumbs,
+      },
+    } as const;
+
+    // Find matching config based on URL
+    const matchingPattern = Object.keys(tabConfig).find(pattern =>
+      source.url.includes(pattern),
+    );
+    const config = matchingPattern
+      ? tabConfig[matchingPattern as keyof typeof tabConfig]
+      : null;
+
+    if (!config) {
+      return source;
+    }
+
+    const breadcrumbs = config.getBreadcrumbs(source.breadcrumbs);
+    const existingTabs = source.tabs ?? [];
+
+    // Check if tab already exists
+    const tabExists = existingTabs.some(existingTab =>
+      typeof existingTab === "string"
+        ? existingTab === config.tab
+        : Array.isArray(existingTab) && existingTab[0] === config.tab,
+    );
+
+    const tabs = tabExists
+      ? existingTabs
+      : [
+          ...existingTabs,
+          [
+            config.tab,
+            {
+              breadcrumbs:
+                breadcrumbs[0] === config.tab
+                  ? breadcrumbs.slice(1)
+                  : breadcrumbs,
+            },
+          ] as const,
+        ];
+
+    return {
+      ...source,
+      breadcrumbs,
+      tabs,
+      shouldOpenInNewTab: config.shouldOpenInNewTab,
+      icon: config.icon ? { builtIn: config.icon } : undefined,
+    };
+  },
+};
+
+const searchSettings: InkeepSearchSettings = {
+  placeholder: "Search",
+  tabs: [
+    "All",
+    "Anchor Docs",
+    "Solana Docs",
+    "Anza Docs",
+    "Stack Exchange",
+    "GitHub",
+  ],
+};
+
+const aiChatSettings: InkeepAIChatSettings = {
+  chatSubjectName: "Anchor",
+  introMessage:
+    "I'm an AI assistant trained on documentation, github repos, and other content. Ask me anything about `Solana`.",
+  aiAssistantAvatar: "https://solana.com/favicon.png",
+  disclaimerSettings: {
+    isEnabled: true,
+    label: "",
+  },
+  toolbarButtonLabels: {
+    getHelp: "Get Support",
+  },
+  getHelpOptions: [
+    {
+      name: "Discord",
+      action: {
+        type: "open_link",
+        url: "https://discord.com/invite/NHHGSXAnXk",
+      },
+      icon: {
+        builtIn: "FaDiscord",
+      },
+    },
+    {
+      name: "Stack Exchange",
+      action: {
+        type: "open_link",
+        url: "https://solana.stackexchange.com/",
+      },
+      icon: {
+        builtIn: "FaStackOverflow",
+      },
+    },
+  ],
+  exampleQuestions: [
+    "How to quickly install Solana dependencies for local development?",
+    "What is the Anchor Framework?",
+    "How to build an Anchor Program?",
+  ],
+};
+
+export function useInkeepConfig() {
+  const [syncTarget, setSyncTarget] = useState<HTMLElement | null>(null);
+
+  // We do this because document is not available in the server
+  useEffect(() => {
+    setSyncTarget(document.documentElement);
+  }, []);
+  return {
+    baseSettings: {
+      ...baseSettings,
+      colorMode: {
+        sync: {
+          target: syncTarget,
+          attributes: ["class"],
+          isDarkMode: (attributes: { class: string | string[] }) =>
+            !!attributes.class?.includes("dark"),
+        },
+      },
+    },
+    searchSettings,
+    aiChatSettings,
+  };
+}

+ 0 - 85
docs/app/inkeep-settings.ts

@@ -1,85 +0,0 @@
-// inkeep apiKeys is public, set to only work on https://www.anchor-lang.com
-export const settings = {
-  baseSettings: {
-    apiKey: "815f8fee7a5da7d98c681626dfbd268bdf7f7dc7cb80f618",
-    integrationId: "cm7nvgfuu001ks6016t3m0gje",
-    organizationId: "org_iKCh36NfndEFpB3M",
-    organizationDisplayName: "Anchor",
-    primaryBrandColor: "#9945ff",
-    customCardSettings: [
-      {
-        filters: {
-          UrlMatch: {
-            ruleType: "PartialUrl",
-            partialUrl: "https://www.anchor-lang.com/docs",
-          },
-        },
-        searchTabLabel: "Anchor Docs",
-      },
-      {
-        filters: {
-          UrlMatch: {
-            ruleType: "PartialUrl",
-            partialUrl: "solana.com",
-          },
-        },
-        searchTabLabel: "Solana Docs",
-      },
-      {
-        filters: {
-          UrlMatch: {
-            ruleType: "PartialUrl",
-            partialUrl: "https://docs.anza.xyz/",
-          },
-        },
-        searchTabLabel: "Anza Docs",
-      },
-      {
-        filters: {
-          UrlMatch: {
-            ruleType: "PartialUrl",
-            partialUrl: "https://solana.stackexchange.com/",
-          },
-        },
-        searchTabLabel: "Stack Exchange",
-      },
-    ],
-  },
-  searchSettings: {
-    shouldOpenLinksInNewTab: true,
-    placeholder: "Search",
-  },
-  aiChatSettings: {
-    chatSubjectName: "Solana",
-    introMessage:
-      "I'm an AI assistant trained on documentation, github repos, and other content. Ask me anything about `Solana`.",
-    botAvatarSrcUrl: "https://solana.com/favicon.png",
-    disclaimerSettings: {
-      isDisclaimerEnabled: true,
-    },
-    actionButtonLabels: {
-      getHelpButtonLabel: "Get Support",
-    },
-    getHelpCallToActions: [
-      {
-        name: "Discord",
-        url: "https://discord.com/invite/NHHGSXAnXk",
-        icon: {
-          builtIn: "FaDiscord",
-        },
-      },
-      {
-        name: "Stack Exchange",
-        url: "https://solana.stackexchange.com/",
-        icon: {
-          builtIn: "FaStackOverflow",
-        },
-      },
-    ],
-    quickQuestions: [
-      "How to set up local environment for Solana development?",
-      "What is the Solana Account Model?",
-      "How to build a Solana Program?",
-    ],
-  },
-};

+ 4 - 4
docs/app/layout.tsx

@@ -1,9 +1,9 @@
 import "./global.css";
-import { RootProvider } from "fumadocs-ui/provider";
+import { Provider } from "./provider";
 import { Inter } from "next/font/google";
 import type { ReactNode } from "react";
 import { GoogleAnalytics } from "@next/third-parties/google";
-import { InkeepScript } from "./components/inkeep-script";
+import { InkeepChatButton } from "./components/inkeep/inkeep-chat-button";
 const inter = Inter({
   subsets: ["latin"],
 });
@@ -12,8 +12,8 @@ export default function Layout({ children }: { children: ReactNode }) {
   return (
     <html lang="en" className={inter.className} suppressHydrationWarning>
       <body className="flex flex-col min-h-screen">
-        <RootProvider>{children}</RootProvider>
-        <InkeepScript />
+        <Provider>{children}</Provider>
+        <InkeepChatButton />
       </body>
       <GoogleAnalytics gaId="G-ZJYNM2WNM0" />
     </html>

+ 20 - 0
docs/app/provider.tsx

@@ -0,0 +1,20 @@
+"use client";
+import { RootProvider } from "fumadocs-ui/provider";
+import dynamic from "next/dynamic";
+import type { ReactNode } from "react";
+
+const SearchDialog = dynamic(
+  () => import("@/app/components/inkeep/inkeep-search"),
+); // lazy load
+
+export function Provider({ children }: { children: ReactNode }) {
+  return (
+    <RootProvider
+      search={{
+        SearchDialog,
+      }}
+    >
+      {children}
+    </RootProvider>
+  );
+}

+ 1 - 1
docs/content/docs/installation.mdx

@@ -15,7 +15,7 @@ development.
 On Mac and Linux, run this single command to install all dependencies.
 
 ```shell title="Terminal"
-curl --proto '=https' --tlsv1.2 -sSfL https://raw.githubusercontent.com/solana-developers/solana-install/main/install.sh | bash
+curl --proto '=https' --tlsv1.2 -sSfL https://solana-install.solana.workers.dev | bash
 ```
 
 <Callout type="warn">

+ 1 - 0
docs/package.json

@@ -9,6 +9,7 @@
     "postinstall": "fumadocs-mdx"
   },
   "dependencies": {
+    "@inkeep/cxkit-react": "^0.5.25",
     "@next/third-parties": "^15.1.4",
     "@svgr/webpack": "^8.1.0",
     "fumadocs-core": "14.5.4",

文件差異過大導致無法顯示
+ 470 - 127
docs/pnpm-lock.yaml


部分文件因文件數量過多而無法顯示