Skip to content

Commit

Permalink
feat: add inkeep
Browse files Browse the repository at this point in the history
  • Loading branch information
marcklingen committed Nov 28, 2024
1 parent 74094fe commit 01d67ea
Show file tree
Hide file tree
Showing 12 changed files with 3,962 additions and 200 deletions.
2 changes: 1 addition & 1 deletion components/GitHubBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const GithubMenuBadge = () => (
<div className="py-1 px-1 block bg-primary/10">
<IconGithub className="group-hover:opacity-80 opacity-100 h-6 w-6" />
</div>
<div className="py-1 text-center text-sm group-hover:opacity-80 opacity-100 w-10">
<div className="hidden sm:block py-1 text-center text-sm group-hover:opacity-80 opacity-100 w-10">
<StarCount />
</div>
</a>
Expand Down
41 changes: 41 additions & 0 deletions components/inkeep/InkeepChatButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useEffect, useState } from "react";
import useInkeepSettings from "./useInkeepSettings";
import type { InkeepChatButtonProps } from "@inkeep/uikit";
import { isChatOpen } from "../supportChat/chat";

export default function InkeepChatButton() {
const [ChatButton, setChatButton] =
useState<(e: InkeepChatButtonProps) => JSX.Element>();

const { baseSettings, aiChatSettings, searchSettings, modalSettings } =
useInkeepSettings();

// load the library asynchronously
useEffect(() => {
const loadChatButton = async () => {
try {
const { InkeepChatButton } = await import("@inkeep/uikit");
setChatButton(() => InkeepChatButton);
} catch (error) {
console.error("Failed to load ChatButton:", error);
}
};

loadChatButton();
}, []);

const chatButtonProps: InkeepChatButtonProps = {
baseSettings,
aiChatSettings,
searchSettings,
modalSettings,
};

return (
ChatButton && (
<div className="w-20">
<ChatButton {...chatButtonProps} />
</div>
)
);
}
74 changes: 74 additions & 0 deletions components/inkeep/InkeepCustomTrigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useCallback, useEffect, useState } from "react";
import useInkeepSettings from "./useInkeepSettings";
import type { InkeepCustomTriggerProps } from "@inkeep/uikit";
import { Search } from "lucide-react";

export default function InkeepCustomTrigger() {
const [isOpen, setIsOpen] = useState(false);
const [CustomTrigger, setCustomTrigger] =
useState<(e: InkeepCustomTriggerProps) => JSX.Element>();

const handleClose = useCallback(() => {
console.log("Modal closed");
setIsOpen(false);
}, []);

const { baseSettings, aiChatSettings, searchSettings, modalSettings } =
useInkeepSettings();

// Handle keyboard shortcuts
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
const isMac = navigator.platform.toLowerCase().includes("mac");
const modifier = isMac ? event.metaKey : event.ctrlKey;

if (modifier && event.key.toLowerCase() === "k") {
event.preventDefault();
setIsOpen(true);
}
};

document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, []);

// load the library asynchronously
useEffect(() => {
const loadCustomTrigger = async () => {
try {
const { InkeepCustomTrigger } = await import("@inkeep/uikit");
setCustomTrigger(() => InkeepCustomTrigger);
} catch (error) {
console.error("Failed to load CustomTrigger:", error);
}
};

loadCustomTrigger();
}, []);

const customTriggerProps: InkeepCustomTriggerProps = {
isOpen,
onClose: handleClose,
baseSettings,
aiChatSettings,
searchSettings,
modalSettings,
};

return (
<div>
<div
onClick={() => setIsOpen(true)}
className="relative flex items-center text-gray-900 dark:text-gray-300 contrast-more:text-gray-800 contrast-more:dark:text-gray-300 max-md:hidden hover:ring-2 hover:ring-gray-300 dark:hover:ring-gray-700 rounded-lg"
>
<div className="rounded-lg px-3 py-2 transition-colors w-full md:w-64 text-base leading-tight md:text-sm bg-black/[.05] dark:bg-gray-50/10 contrast-more:border contrast-more:border-current">
Search or ask...
</div>
<kbd className="absolute my-1.5 select-none ltr:right-1.5 rtl:left-1.5 h-5 rounded bg-white px-1.5 font-mono text-[11px] font-medium text-gray-500 border dark:border-gray-100/20 dark:bg-black/50 contrast-more:border-current contrast-more:text-current contrast-more:dark:border-current items-center gap-1 flex max-sm:hidden">
<span className="text-xs"></span>K
</kbd>
</div>
{CustomTrigger && <CustomTrigger {...customTriggerProps} />}
</div>
);
}
40 changes: 40 additions & 0 deletions components/inkeep/InkeepSearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useEffect, useState } from "react";
import useInkeepSettings from "./useInkeepSettings";
import type { InkeepSearchBarProps } from "@inkeep/uikit";

export default function InkeepSearchBar() {
const [SearchBar, setSearchBar] =
useState<(e: InkeepSearchBarProps) => JSX.Element>();

const { baseSettings, aiChatSettings, searchSettings, modalSettings } =
useInkeepSettings();

// load the library asynchronously
useEffect(() => {
const loadSearchBar = async () => {
try {
const { InkeepSearchBar } = await import("@inkeep/uikit");
setSearchBar(() => InkeepSearchBar);
} catch (error) {
console.error("Failed to load SearchBar:", error);
}
};

loadSearchBar();
}, []);

const searchBarProps: InkeepSearchBarProps = {
baseSettings,
aiChatSettings,
searchSettings,
modalSettings,
};

if (!SearchBar) return null;

return (
<div className="h-9 overflow-hidden">
<SearchBar {...searchBarProps} />
</div>
);
}
109 changes: 109 additions & 0 deletions components/inkeep/useInkeepSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type {
InkeepAIChatSettings,
InkeepSearchSettings,
InkeepBaseSettings,
InkeepModalSettings,
} from "@inkeep/uikit";
import { useTheme } from "nextra-theme-docs";
import { openChat } from "../supportChat";
import { MessageCircle } from "lucide-react";

type InkeepSharedSettings = {
baseSettings: InkeepBaseSettings;
aiChatSettings: InkeepAIChatSettings;
searchSettings: InkeepSearchSettings;
modalSettings: InkeepModalSettings;
};

const useInkeepSettings = (): InkeepSharedSettings => {
const { resolvedTheme } = useTheme();

const baseSettings: InkeepBaseSettings = {
apiKey: process.env.NEXT_PUBLIC_INKEEP_API_KEY!,
integrationId: process.env.NEXT_PUBLIC_INKEEP_INTEGRATION_ID!,
organizationId: process.env.NEXT_PUBLIC_INKEEP_ORGANIZATION_ID!,
primaryBrandColor: "#E11312", // your brand color, widget color scheme is derived from this
organizationDisplayName: "Langfuse",
// ...optional settings
colorMode: {
forcedColorMode: resolvedTheme, // to sync dark mode with the widget
},
theme: {
components: {
SearchBarTrigger: {
defaultProps: {
size: "shrink", // 'expand' 'compact' 'shrink' 'medium'
variant: "subtle", // 'emphasized' 'subtle',
},
},
},
},
};

const modalSettings: InkeepModalSettings = {
// optional settings
};

const searchSettings: InkeepSearchSettings = {
placeholder: "Search...",
};

const aiChatSettings: InkeepAIChatSettings = {
// optional settings
chatSubjectName: "Langfuse",
botAvatarSrcUrl: "/icon256.png", // use your own bot avatar
// includeAIAnnotations: {
// shouldEscalateToSupport: true,
// },
// aiAnnotationPolicies: {
// shouldEscalateToSupport: [
// {
// threshold: "STANDARD", // "STRICT" or "STANDARD"
// action: {
// type: "SHOW_SUPPORT_BUTTON",
// label: "Contact Support",
// icon: { builtIn: "LuUsers" },
// action: {
// type: "INVOKE_CALLBACK",
// callback: () => openChat(),
// },
// },
// },
// ],
// },
getHelpCallToActions: [
{
name: "Chat with us",
type: "INVOKE_CALLBACK",
callback: () => openChat(),
icon: {
builtIn: "IoChatbubblesOutline",
},
},
{
name: "GitHub Support",
url: "https://langfuse.com/gh-support",
icon: {
builtIn: "FaGithub",
},
},
{
name: "Community Discord",
url: "https://langfuse.com/discord",
icon: {
builtIn: "FaDiscord",
},
},
],
quickQuestions: [
"How to use the Python decorator to trace my LLM app?",
"How to use Langfuse with Vercel AI SDK?",
"How to mask sensitive LLM data?",
"How to set up LLM-as-a-judge evals?",
],
};

return { baseSettings, aiChatSettings, searchSettings, modalSettings };
};

export default useInkeepSettings;
4 changes: 2 additions & 2 deletions components/logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ export function Logo() {
alt="Langfuse Logo"
width={120}
height={20}
className="hidden dark:block"
className="hidden dark:block max-w-28 sm:max-w-none"
/>
<Image
src="/langfuse_logo.svg"
alt="Langfuse Logo"
width={120}
height={20}
className="block dark:hidden"
className="block dark:hidden max-w-28 sm:max-w-none"
/>
<style jsx>{`
div {
Expand Down
11 changes: 10 additions & 1 deletion components/supportChat/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const CrispChat = () => {

useEffect(() => {
if (process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID) {
Crisp.configure(process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID);
Crisp.configure(process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID, {
autoload: false,
});
Crisp.chat.onChatInitiated(() => {
posthog.capture("support_chat:initiated");
});
Expand Down Expand Up @@ -37,3 +39,10 @@ export const openChat = () => {
Crisp.chat.open();
}
};

export const isChatOpen = () => {
if (process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID) {
return Crisp.chat.isChatOpened();
}
return false;
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@calcom/embed-react": "^1.5.1",
"@headlessui/react": "^2.2.0",
"@hookform/resolvers": "^3.9.1",
"@inkeep/uikit": "^0.3.18",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
Expand Down
2 changes: 2 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Hubspot, hsPageView } from "@/components/analytics/hubspot";
import { SpeedInsights } from "@vercel/speed-insights/next";
import { GeistSans } from "geist/font/sans";
import { GeistMono } from "geist/font/mono";
import InkeepChatButton from "@/components/inkeep/InkeepChatButton";

export default function App({ Component, pageProps }) {
const router = useRouter();
Expand Down Expand Up @@ -45,6 +46,7 @@ export default function App({ Component, pageProps }) {
</PostHogProvider>
<Hubspot />
<SpeedInsights />
<InkeepChatButton />
<Script
src="https://app.termly.io/resource-blocker/488cc3b0-ed5a-4e9d-81f2-76014dcae784?autoBlock=on"
strategy="beforeInteractive"
Expand Down
Loading

0 comments on commit 01d67ea

Please sign in to comment.