- The documentation page you’re looking for doesn’t exist or has moved. -
- - Back to Documentation - -- The guides page you're looking for doesn't exist or has moved. -
- - Back to Guides - -- The integrations page you're looking for doesn't exist or has moved. -
- - Back to Integrations - -+
+
| - {selectedTiers.map((tier, index) => ( - | - {tier.name} - | - ))} - > - )} -
|---|
| + {selectedTiers.map((tier, index) => ( + | + {tier.name} + | + ))} +
|---|
|
{visibleSections.map((section) => (
+
-
+
{/* Deployment Options Tabs */}
{variant === "cloud" &&
-
- );
-}
diff --git a/components/inkeep/InkeepCustomTrigger.tsx b/components/inkeep/InkeepCustomTrigger.tsx
deleted file mode 100644
index 9049010741..0000000000
--- a/components/inkeep/InkeepCustomTrigger.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import React, { useEffect, useState } from "react";
-import useInkeepSettings from "./useInkeepSettings";
-import type { InkeepModalSearchAndChatProps } from "@inkeep/cxkit-react";
-
-export default function InkeepCustomTrigger() {
- const [isOpen, setIsOpen] = useState(false);
- const [CustomTrigger, setCustomTrigger] =
- useState<(e: InkeepModalSearchAndChatProps) => React.ReactElement>();
-
- 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 { InkeepModalSearchAndChat } = await import("@inkeep/cxkit-react");
- setCustomTrigger(() => InkeepModalSearchAndChat);
- } catch (error) {
- console.error("Failed to load CustomTrigger:", error);
- }
- };
-
- loadCustomTrigger();
- }, []);
-
- const customTriggerProps: InkeepModalSearchAndChatProps = {
- baseSettings,
- aiChatSettings,
- searchSettings,
- modalSettings: {
- ...modalSettings,
- isOpen,
- onOpenChange: setIsOpen,
- },
- };
-
- return (
-
-
- );
-}
diff --git a/components/inkeep/InkeepEmbeddedChat.tsx b/components/inkeep/InkeepEmbeddedChat.tsx
deleted file mode 100644
index f4bfed9270..0000000000
--- a/components/inkeep/InkeepEmbeddedChat.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-"use client";
-
-import dynamic from "next/dynamic";
-import type { InkeepEmbeddedChatProps } from "@inkeep/cxkit-react";
-import useInkeepSettings from "./useInkeepSettings";
-
-const EmbeddedChat = dynamic(
- () => import("@inkeep/cxkit-react").then((mod) => mod.InkeepEmbeddedChat),
- {
- ssr: false,
- loading: () => 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"
- >
-
- {CustomTrigger &&
- Search or ask...
-
-
- ⌘K
-
- loading... , // optional: loading animation component
- }
-);
-
-const css = String.raw;
-
-function InkeepEmbeddedChat() {
- const { baseSettings, aiChatSettings } = useInkeepSettings();
-
- const embeddedChatProps: InkeepEmbeddedChatProps = {
- baseSettings: {
- ...baseSettings,
- theme: {
- styles: [
- {
- key: '1',
- type: 'style',
- value: css`
- .ikp-ai-chat-wrapper {
- width: 100%;
- height: auto;
- max-height: 100%;
- }
- `,
- },
- ],
- },
- },
- aiChatSettings,
- };
-
- return
-
+
- );
+ )
}
diff --git a/components/inkeep/ai-chat-shared.tsx b/components/inkeep/ai-chat-shared.tsx
new file mode 100644
index 0000000000..e5d5074204
--- /dev/null
+++ b/components/inkeep/ai-chat-shared.tsx
@@ -0,0 +1,120 @@
+'use client';
+
+import type { ComponentProps } from 'react';
+import { cn } from '@/lib/utils';
+import { Text } from '@/components/ui/text';
+import { Link } from '@/components/ui/link';
+import type { InkeepUIMessage, ProvideLinksData } from '@/lib/ai/inkeep-qa-schema';
+import { Markdown } from './markdown';
+
+export const AI_CHAT_EXAMPLE_QUESTIONS = [
+ 'How can Langfuse help me?',
+ 'How to use the Python decorator for tracing?',
+ 'How to set up LLM-as-a-judge evals?',
+] as const;
+
+const roleName: Record
+
{
+ if (captureClicks) e.stopPropagation();
+ onClick?.(e);
+ }}
+ >
+
+ );
+}
+
+type AIChatEmptyStateProps = {
+ onPickQuestion: (question: string) => void;
+};
+
+export function AIChatEmptyState({ onPickQuestion }: AIChatEmptyStateProps) {
+ return (
+ + {roleName[message.role] ?? 'unknown'} + +
+
+ {links && links.length > 0 && (
+
+ {links.map((item, i) => (
+
+
+ )}
+ {item.title} +Reference {item.label} + + ))} +
+
+ );
+}
diff --git a/components/inkeep/ask-ai-button.tsx b/components/inkeep/ask-ai-button.tsx
new file mode 100644
index 0000000000..f657de2e0d
--- /dev/null
+++ b/components/inkeep/ask-ai-button.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import { AISearchTrigger, useAISearchContext } from "@/components/inkeep/search";
+import { cn } from "@/lib/utils";
+
+export const AskAIButton = () => {
+ return (
+
+
+
+ {AI_CHAT_EXAMPLE_QUESTIONS.map((question) => (
+
+
+
+ );
+};
diff --git a/components/inkeep/embedded-chat.tsx b/components/inkeep/embedded-chat.tsx
new file mode 100644
index 0000000000..50e320ea3a
--- /dev/null
+++ b/components/inkeep/embedded-chat.tsx
@@ -0,0 +1,169 @@
+'use client';
+
+import {
+ type ComponentProps,
+ type SyntheticEvent,
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+import { Loader2, RefreshCw, Send } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+import { Text } from '@/components/ui/text';
+import { Link } from '@/components/ui/link';
+import { AIChatEmptyState, AIChatMessage } from './ai-chat-shared';
+import { useChatContext, buildUserMessage } from './search-context';
+
+function EmbeddedTextarea(props: ComponentProps<'textarea'>) {
+ const shared = cn('col-start-1 row-start-1', props.className);
+
+ return (
+
+
+
+ );
+}
+
+export function EmbeddedAIChat() {
+ const chat = useChatContext();
+ const messages = chat.messages.filter((msg) => msg.role !== 'system');
+ const isLoading = chat.status === 'streaming' || chat.status === 'submitted';
+ const isStreaming = chat.status === 'streaming';
+
+ const [input, setInput] = useState('');
+ const scrollRef = useRef
+ {`${props.value?.toString() ?? ''}\n`}
+
+
+
+ );
+}
diff --git a/components/inkeep/markdown.tsx b/components/inkeep/markdown.tsx
new file mode 100644
index 0000000000..c61b6d0cd6
--- /dev/null
+++ b/components/inkeep/markdown.tsx
@@ -0,0 +1,124 @@
+import { remark } from 'remark';
+import remarkGfm from 'remark-gfm';
+import remarkRehype from 'remark-rehype';
+import { toJsxRuntime } from 'hast-util-to-jsx-runtime';
+import {
+ Children,
+ type ComponentProps,
+ type ReactElement,
+ type ReactNode,
+ Suspense,
+ use,
+ useDeferredValue,
+} from 'react';
+import { Fragment, jsx, jsxs } from 'react/jsx-runtime';
+import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock';
+import defaultMdxComponents from 'fumadocs-ui/mdx';
+import { visit } from 'unist-util-visit';
+import type { ElementContent, Root, RootContent } from 'hast';
+
+export interface Processor {
+ process: (content: string) => Promise
+
+
+
+ {messages.length === 0 ? (
+
+
+
+ {messages.map((item) => (
+
+ )}
+
+
+ {messages.length > 0 && (
+
+
+ {!isStreaming && messages.at(-1)?.role === 'assistant' && (
+
+ )}
+
+
+ );
+}
+
+// ─── Input actions (retry / clear) ─────────────────────────────────────────────
+
+function AISearchInputActions() {
+ const { messages, status, setMessages, regenerate } = useChatContext();
+ const isLoading = status === 'streaming';
+
+ if (messages.length === 0) return null;
+
+ return (
+ <>
+ {!isLoading && messages.at(-1)?.role === 'assistant' && (
+
+
+
+
+
+
+ );
+}
+
+// ─── Scrollable message list ───────────────────────────────────────────────────
+
+function ScrollList(props: Omit
+ {`${props.value?.toString() ?? ''}\n`}
+
+
+ {props.children}
+
+ );
+}
+
+// ─── Message list panel (empty state + messages) ───────────────────────────────
+
+function AISearchPanelList({ className, style, ...props }: ComponentProps<'div'>) {
+ const chat = useChatContext();
+ const messages = chat.messages.filter((msg) => msg.role !== 'system');
+
+ const sendExampleQuestion = (question: string) => {
+ void chat.sendMessage(buildUserMessage(question));
+ };
+
+ return (
+
+ {messages.map((item) => (
+
+ )}
+ setOpen(false)}
+ />
+
+
diff --git a/components/jp-cloud/index.tsx b/components/jp-cloud/index.tsx
index 50784f0fe6..af2875a834 100644
--- a/components/jp-cloud/index.tsx
+++ b/components/jp-cloud/index.tsx
@@ -1,4 +1,3 @@
-import { Background } from "@/components/Background";
import { JpCloudHero } from "./JpCloudHero";
import { JpCloudValueProps } from "./JpCloudValueProps";
import { JpCloudSecurity } from "./JpCloudSecurity";
@@ -9,17 +8,14 @@ import { JpCloudCTA } from "./JpCloudCTA";
export function JpCloud() {
return (
- <>
-
+
+
+
+
+
+
+
+
-
+
-
+
+
+ );
+}
+
+export function DocsSecondaryNav() {
+ const pathname = usePathname();
+ return (
+
+
+
+ );
+}
diff --git a/components/layout/FluxLayoutNoPanel.tsx b/components/layout/FluxLayoutNoPanel.tsx
deleted file mode 100644
index fbf8a795d5..0000000000
--- a/components/layout/FluxLayoutNoPanel.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-"use client";
-
-import { DocsLayout, type DocsLayoutProps } from "fumadocs-ui/layouts/flux";
-
-/** Wrapper around fumadocs flux DocsLayout that suppresses the NavigationPanel.
- * Must be a Client Component because renderNavigationPanel is a function prop. */
-export function FluxLayoutNoPanel(props: Omit |
|---|