# Chat Completions API Inkeep's Chat Completion API endpoints make it easy to develop chatbot or copilot experiences powered by your own knowledge base. Because these endpoints are compatible with OpenAI's Chat Completion API format, you can use them with most LLM application frameworks, libraries, or SDKs with zero code changes. For example, you can build a: * ChatGPT-like UI using the [Next.js Chatbot template](https://vercel.com/templates/next.js/nextjs-ai-chatbot) * Product-specific copilot using the [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction) or [LangChain](https://github.com/langchain-ai/langchainjs) * An automation for replying to customer emails or as a plugin to your support platform using [OpenAI's TypeScript SDK](https://github.com/openai/openai-node). Check out our [examples](https://github.com/inkeep/inkeep-chat-api-examples). ## Available modes We offer various modes tailored for different use cases. | Mode Name | Description | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `inkeep-qa` | Provides sensible defaults for customer-facing support bot scenarios. | | `inkeep-context` | A fully "passthrough" mode that injects Inkeep's RAG context in calls to base models. Best for when you need custom tool (function) calls or full prompting control for custom LLM applications, agents or workflows. | ## Using the API To use with an OpenAI compatible client, simply: 1. Customize the `baseUrl` to `https://api.inkeep.com/v1` 2. Specify the mode by setting `model` to a value in the format of `{inkeep-mode}-{model}` If you'd like to let Inkeep manage which model is used * `inkeep-qa-expert` * `inkeep-context-expert` If you'd like to pin the service against a preferred LLM model, use one of: * `inkeep-qa-sonnet-3-5` (recommended) * `inkeep-qa-gpt-4o` * `inkeep-qa-gpt-4-turbo` * `inkeep-context-sonnet-3-5` (recommended) * `inkeep-context-gpt-4-turbo` * `inkeep-context-gpt-4o` All modes use the OpenAI chat completion API format. ## Question Answer Mode The `qa` mode is specifically tailored for customer-facing responses like support chatbots or auto-reply workflows. This mode comes with Inkeep's sensible built-in behavior for: * format of citations * tone * techniques to reduce hallucinations * keeping answers scoped to your product and service QA models are best suited for when you want to develop your own chat UX or automations without worrying about how to prompt or use a model. The `qa` mode responds with an AI assistant message (`content`) and two additional pieces of information: `links` and `aiAnnotations` (provided as **tools**). | Component | Description | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `content` | Contains the conversational answer of the AI assistant as any normal OpenAI chat completion request. This is what you would display as the AI assistant answer in a traditional chat UI. | | `provideLinks` | Provides a list of links (sources) used by the AI assistant to generate a response. You can use this to display citations for an answer. | | `provideAIAnnotations` | Provides labels for the the response, like `answerConfidence`. `answerConfidence` indicates how confident the AI assistant is in its response. You can use this to, for example, conditionally show answers to an end-user only if the AI assistant is confident. | ```ts inkeep-qa-tools-schema.ts import { z } from "zod"; /* provideLinks tool schema */ const InkeepRecordTypes = z.enum([ 'documentation', 'site', 'discourse_post', 'github_issue', 'github_discussion', 'stackoverflow_question', 'discord_forum_post', 'discord_message', 'custom_question_answer', ]); const LinkType = z.union([ InkeepRecordTypes, z.string() // catch all ]); const LinkSchema = z.object({ label: z.string().nullish(), // the value of the footnote, e.g. `1` url: z.string(), title: z.string().nullish(), description: z.string().nullish(), type: LinkType.nullish(), breadcrumbs: z.array(z.string()).nullish(), }).passthrough(); export const LinksSchema = z.array(LinkSchema).nullish(); export const LinksToolSchema = z.object({ links: LinksSchema, }); /* provideAIAnnotations tool schema */ const KnownAnswerConfidence = z.enum([ 'very_confident', 'somewhat_confident', 'not_confident', 'no_sources', 'other', ]); const AnswerConfidence = z.union([KnownAnswerConfidence, z.string()]).nullish(); // evolvable const AIAnnotationsToolSchema = z.object({ answerConfidence: AnswerConfidence, }).passthrough(); export const ProvideAIAnnotationsToolSchema = z.object({ aiAnnotations: AIAnnotationsToolSchema, }); ``` ## Context Mode The `inkeep-context` mode works like a "passthrough" proxy that injects Inkeep's RAG context to a call to an underlying model from Anthropic or OpenAI. These endpoints are fully compatible with all chat completion endpoint functionality like tool calling, JSON mode, image inputs, etc. This is great for when you're developing a custom AI agent, LLM-application, or workflow and require full control of outputs and custom tool calls but would like a managed RAG system. This mode is unopinionated and provides a high-degree of flexibility; therefore, it requires a similar level of prompting, testing, and experimentation required of any LLM application. # Vercel AI SDK With the Inkeep `context` mode, you can leverage all of the capabilities provided by a normal OpenAI API endpoint but "grounded" with context about your product. This gives you the full flexibility of creating any LLM application: custom copilots, AI workflows, etc., all backed by your own data. Structured data can be particularly powerful in rendering dynamic UI components as part of a conversational experience. As a basic example, instead of having a support bot that answers with a single `content` payload, we can instead define a response to be returned as a series of structured steps. The example shown below illustrates how to accomplish that with [streamObject](https://sdk.vercel.ai/docs/reference/ai-sdk-core/stream-object) and Inkeep's `context` model. ```ts index.ts import { z } from 'zod'; import { streamObject } from 'ai'; import { createOpenAI } from '@ai-sdk/openai' import dotenv from 'dotenv'; dotenv.config(); if (!process.env.INKEEP_API_KEY) { throw new Error('INKEEP_API_KEY is required'); } const openai = createOpenAI({ apiKey: process.env.INKEEP_API_KEY, baseURL: 'https://api.inkeep.com/v1' }) const StepSchema = z.object({ steps: z.array(z.object({ step: z.string(), description: z.string() })) }) async function getResponseFromAI() { const result = await streamObject({ model: openai('inkeep-context-sonnet-3-5'), schema: StepSchema, messages: [ { role: 'system', content: 'Generate step-by-step instructions to answer the user question about Inkeep only based on the information sources. Break it down to be as granular as possible. Always generate more than one step.' }, ] }) const { partialObjectStream } = result for await (const partialObject of partialObjectStream) { console.clear(); console.log(partialObject.steps) } } getResponseFromAI(); ``` # OpenAI SDK Here's an example of how to use [Chat Completions](https://platform.openai.com/docs/guides/chat-completions) with one of Inkeep's `qa` models ```ts index.ts import OpenAI from 'openai'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { LinksToolSchema } from './LinksToolSchema'; import dotenv from 'dotenv'; dotenv.config(); if (!process.env.INKEEP_API_KEY) { throw new Error('INKEEP_API_KEY is required'); } const client = new OpenAI({ baseURL: 'https://api.inkeep.com/v1/', apiKey: process.env.INKEEP_API_KEY, // dangerouslyAllowBrowser: true, use this setting if you are using this in browser }); async function getResponseFromAI() { const result = await client.chat.completions.create({ model: 'inkeep-qa-sonnet-3-5', messages: [{ role: 'user', content: 'How do I get started with Inkeep?' }], stream: true, tools: [ { type: 'function', function: { name: 'provideLinks', description: 'Provides links', parameters: zodToJsonSchema(LinksToolSchema.extend({ text: z.string(), })), }, }, ], tool_choice: 'auto', }); let toolCalls: any[] = []; for await (const chunk of result) { const c = chunk.choices[0] process.stdout.write(c.delta.content || ''); if (c.delta?.tool_calls) { c.delta.tool_calls.forEach(toolCall => { const existingCall = toolCalls[toolCall.index ?? 0]; if (existingCall) { // Update existing call if (toolCall.function) { existingCall.function = existingCall.function || {}; if (toolCall.function.name) existingCall.function.name = toolCall.function.name; if (toolCall.function.arguments) { existingCall.function.arguments = (existingCall.function.arguments || '') + toolCall.function.arguments; } } if (toolCall.type) existingCall.type = toolCall.type; } else { // Add new call toolCalls[toolCall.index ?? 0] = toolCall; } }); } } // Check if the function is called in the response if (toolCalls && toolCalls.length > 0) { const provideLinksCall = toolCalls.find(call => call.function.name === 'provideLinks'); if (provideLinksCall) { const functionArgs = JSON.parse(provideLinksCall.function.arguments); await provideLinks(functionArgs); } else { console.log('No provideLinks tool call found'); } } } const provideLinks = async ({ links, text }: { links?: (typeof LinksToolSchema._type)['links']; text: string }) => { console.log('\nLinks used in response: ', links); return Promise.resolve(); }; getResponseFromAI(); ``` # Using `inkeep-qa` models with the Vercel AI SDK ## Using useChat and a route [`useChat`](https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat) is cross-platform hook that helps you create a conversational experience with the Vercel AI SDK with React, Svelte, Vue, and Solid frontends. To use useChat, first create an API route using Next.js. Here's where we'll call Inkeep. ### API Route ```tsx app/api/chat/route.ts import { LinksTool } from '@/lib/chat/inkeep-qa-schema' import { createOpenAI } from '@ai-sdk/openai' import { streamText } from 'ai' export const runtime = 'edge' const openai = createOpenAI({ apiKey: process.env.INKEEP_API_KEY, baseURL: 'https://api.inkeep.com/v1' }) export async function POST(req: Request) { const reqJson = await req.json() const result = await streamText({ model: openai('inkeep-qa-sonnet-3-5'), messages: reqJson.messages.map((message: any) => ({ role: message.role, content: message.content, name: 'inkeep-qa-user-message', id: message.id })), tools: { // to get the citation information provideLinks: { ...LinksTool } }, toolChoice: 'auto' }) return result.toAIStreamResponse() } ``` ### Client ```tsx app/page.tsx 'use client' import { useChat } from 'ai/react' export default function Page() { const { messages, isLoading, input, handleSubmit, handleInputChange } = useChat({ streamMode: 'stream-data', sendExtraMessageFields: true, onResponse(response) { if (response.status === 401) { console.error(response.statusText) } } }) return ( <> {messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.content}
))} {isLoading &&
Loading...
}
) } ``` ## Using Server Actions (AI SDK Actions) [`streamUI`](https://sdk.vercel.ai/docs/reference/ai-sdk-rsc/stream-ui) is another way to use the Vercel AI SDK, but with React Server Components. This lets you stream entire UI components, not just the text that is then parsed and rendered on the client. This example illustrates how to render assistant messages and a "sources" list provided by the `provideLinks` tool. ### Server Action ```tsx lib/chat/actions.ts import 'server-only' import { createAI, getMutableAIState, streamUI } from 'ai/rsc' import { createOpenAI } from '@ai-sdk/openai' import { Message } from 'ai' import { nanoid } from './utils' import { LinksTool } from './inkeep-qa-schema' const openai = createOpenAI({ apiKey: process.env.INKEEP_API_KEY, baseURL: 'https://api.inkeep.com/v1' }) async function submitUserMessage(content: string) { 'use server' const aiState = getMutableAIState() aiState.update({ ...aiState.get(), messages: [ ...aiState.get().messages, { id: nanoid(), role: 'user', content } ] }) const answerMessageId = nanoid() const result = await streamUI({ model: openai('inkeep-qa-sonnet-3-5'), messages: [ ...aiState.get().messages.map((message: any) => ({ role: message.role, content: message.content, name: 'inkeep-qa-user-message', id: message.id })) ], text: ({ content }) => { const assistantAnswerMessage = { id: answerMessageId, role: 'assistant', content, name: 'inkeep-qa-assistant-message' } as Message const currentMessages = aiState.get().messages const lastMessage = currentMessages[currentMessages.length - 1] aiState.update({ ...aiState.get(), messages: lastMessage?.id === answerMessageId ? [...currentMessages.slice(0, -1), assistantAnswerMessage] : [...currentMessages, assistantAnswerMessage] }) return
{assistantAnswerMessage.content}
}, tools: { provideLinks: { ...LinksTool, generate: async ({ links }) => { // render sources UI once tool is complete const currentMessages = aiState.get().messages const lastMessage = currentMessages[currentMessages.length - 1] const lastMessageWithToolResults = { ...lastMessage, toolInvocations: [ { toolName: 'provideLinks', result: links } ] } as Message aiState.done({ ...aiState.get(), messages: [ ...currentMessages.slice(0, -1), lastMessageWithToolResults ] }) return
{lastMessageWithToolResults.content}
} } }, toolChoice: 'auto' }) return { id: nanoid(), display: result.value } } export type AIState = { chatId: string messages: Message[] } export type UIState = { id: string display: React.ReactNode }[] export const AI = createAI({ actions: { submitUserMessage }, initialUIState: [], initialAIState: { chatId: nanoid(), messages: [] } }) ``` ## Page.tsx ```tsx app/page.tsx import { Chat } from '@/components/chat' import { AI } from '@/lib/chat/actions' import { nanoid } from '@/lib/chat/utils' export default async function IndexPage() { const id = nanoid() return ( ) } ```
### Client ```tsx app/components/chat.tsx 'use client' import { useUIState, useAIState, useActions } from 'ai/rsc' import { Message } from 'ai' import { UIState } from '@/lib/chat/actions' import { nanoid } from '@/lib/chat/utils' export interface ChatProps extends React.ComponentProps<'div'> { initialMessages?: Message[] id?: string } export function Chat({ id, className }: ChatProps) { const [messages] = useUIState() return (
) } export interface ChatList { messages: UIState } export function ChatList({ messages }: ChatList) { const [messagesUIState, setMessagesUIState] = useUIState() const { submitUserMessage } = useActions() const handleSubmit = async (e: any) => { e.preventDefault() const form = e.currentTarget const input = form.elements.namedItem('prompt') as HTMLInputElement const value = input.value.trim() if (!value) return input.value = '' // Clear the input after submission const userMessage = { id: nanoid(), content: value, role: 'user' } as Message // Optimistically add user message UI setMessagesUIState(currentMessages => [ ...currentMessages, { id: nanoid(), display:
{userMessage.content}
} ]) // Submit and get response message const responseMessage = await submitUserMessage(value) setMessagesUIState(currentMessages => [...currentMessages, responseMessage]) } return ( <>
{messages.map(message => (
{message.display}
))}
) } ``` # Add analytics to any OpenAI compatible chat With Inkeep's Analytics API, you can log your AI chat conversations to: 1. Get Inkeep's analytics and reporting for the chats 2. Provide and track "Thumbs up/down" feedback 3. Power "Share" or "Saved Chats" experiences The Inkeep Analytics API is designed to be flexible and can log any OpenAI-compatible conversation, including those with tool calls or custom functionality. If you're using [Inkeep's Chat API](/ai-api/chat-completions-api) to create your own chat UX or custom copilot, you should use the Analytics API to enable the same reporting as provided by the `@inkeep/uikit` component library. ## Log a conversation ### Initial message To log a new conversation, make a POST request to the `/conversations` endpoint. ```javascript const initialConversationData = { id: "conv_1234", // optional type: "openai", messages: [ { role: "user", content: "Can I log any OpenAI-compatible conversation?" }, { role: "assistant", content: "Yes, our analytics engine is agnostic. You can log full conversations of your agent or custom copilot, even if not all interactions hit Inkeep's API endpoint." } ], properties: { // Add any custom properties here }, userProperties: { // Add any user-specific properties here } }; let currentConversation = await fetch('https://api.analytics.inkeep.com/conversations', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify(initialConversationData) }).then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }); console.log(currentConversation); ``` ### Follow-up messages To log additional messages in an existing conversation, include the `id` returned from the initial submission: ```javascript const continueConversationData = { id: currentConversation.id, type: "openai", messages: [ ...currentConversation.messages, // Add new messages { role: "user", content: "Does that mean it supports logging tool calls?" }, { role: "assistant", content: "Yup, Inkeep's Analytics API is fully compatible with OpenAI chat completions endpoints, including tool calling." } ] }; currentConversation = await fetch('https://api.analytics.inkeep.com/conversations', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify(continueConversationData) }).then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }); console.log(currentConversation); ``` You can provide your own IDs for conversations and messages, or Inkeep will auto-generate them. For logging continuous exchanges in a conversation, make sure to retain and use the same conversation ID and always include the entire message history. `POST /conversation` works like an upsert operation. ## Submit feedback Use the `/feedback` endpoint to log 👍 / 👎 feedback from your UI. ```javascript const feedbackData = { type: "positive", // or "negative" messageId: "{assistant_message_id}", // e.g. currentConversation.messages[1].id reasons: [ { label: "accurate_code_snippet", details: "The code worked perfectly." } ], userProperties: { // Add any user-specific properties here } }; fetch('https://api.analytics.inkeep.com/feedback', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify(feedbackData) }) .then(response => response.json()) .then(data => console.log(data)); ``` ## Monitor To view analytics for your logged conversations and feedback: 1. Go to [portal.inkeep.com](https://portal.inkeep.com) 2. Navigate to **Projects** and select the relevant one 3. View the **Chat Sessions**, **Insights**, and **Analytics** tabs ## Use for "Share" or "Saved Chats" You can use the `GET /conversations` endpoint to retrieve logged conversations and power "Share" or "Saved Chats" features. Here's an example of how to fetch a specific conversation: ```ts fetch('https://api.analytics.inkeep.com/conversations/{existing_conversation_id}', { method: 'GET', headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }) .then(response => response.json()) .then(data => { // Use the conversation data to rehydrate your UI console.log(data); }); ``` The response will contain detailed information about the conversation: ```json { "id": "conv_123456789", "type": "openai", "createdAt": "2024-07-11T10:15:30.123Z", "updatedAt": "2024-07-11T10:16:45.678Z", "projectId": "proj_987654321", "integrationId": "intg_246813579", "properties": { "source": "web_chat" }, "userProperties": { "userId": "user_135792468" }, "messages": [ { "id": "msg_1", "type": "openai", "conversationId": "conv_123456789", "createdAt": "2024-07-11T10:15:30.123Z", "updatedAt": "2024-07-11T10:15:30.123Z", "role": "user", "content": "Can I log any OpenAI-compatible conversation?", "name": null, "links": null, "properties": {}, "userProperties": {} }, { "id": "msg_2", "type": "openai", "conversationId": "conv_123456789", "createdAt": "2024-07-11T10:15:40.456Z", "updatedAt": "2024-07-11T10:15:40.456Z", "role": "assistant", "content": "Yes, our analytics engine is agnostic. You can log full conversations of your agent or custom copilot, even if not all interactions hit Inkeep's API endpoint.", "name": null, "links": null, "properties": {}, "userProperties": {} }, { "id": "msg_3", "type": "openai", "conversationId": "conv_123456789", "createdAt": "2024-07-11T10:16:15.789Z", "updatedAt": "2024-07-11T10:16:15.789Z", "role": "user", "content": "Does that mean it supports logging tool calls?", "name": null, "links": null, "properties": {}, "userProperties": {} }, { "id": "msg_4", "type": "openai", "conversationId": "conv_123456789", "createdAt": "2024-07-11T10:16:45.678Z", "updatedAt": "2024-07-11T10:18:45.678Z", "role": "assistant", "content": "Yup, Inkeep's Analytics API is fully compatible with OpenAI chat completion endpoints, including tool calling.", "name": null, "links": null, "properties": {}, "userProperties": {} } ], "messagesOpenAIFormat": [ { "role": "user", "content": "Can I log any OpenAI-compatible conversation?" }, { "role": "assistant", "content": "Yes, Inkeep's analytics engine is agnostic. You can log full conversations of your agent or custom copilot, even if not all interactions hit Inkeep's API endpoint." }, { "role": "user", "content": "Does that mean it supports logging tool calls?" }, { "role": "assistant", "content": "Yup, Inkeep's Analytics API is fully compatible with OpenAI chat completions endpoints, including tool calling." } ] } ``` This response includes all extended details about the conversation and its messages, including any properties you associated with it. The `messagesOpenAIFormat` property provides the messages of the conversation in a format compatible with OpenAI's chat completions API. You can use this to implement **Share a chat**, **Saved Chats**, or any other functionality that requires retrieving an existing chat. Here's how: * **Share a chat** - Create an entry point in your application or UI that parses a query parameter, e.g. `?chatId=`. Then, use the `GET /conversations/{id}` endpoint to get the last state of a conversation and hydrate the chat UI in your own application. If you want to be able to "fork" shared chats, then simply use a different `id` when logging the continuation of a shared conversation to `POST /conversations`. This will log it as a separate conversation from the original. * **Saved Chats** - Whenever a user creates a new conversation, associate that `id` with the user in your database or application. This way, you have a relationship of `chats<>user` and can display a user's history in your UI. You can also store that relationship in a user's browser session for unauthenticated contexts. ## Log usage events In addition to logging conversations and feedback, you can also track specific usage events related to conversations or messages using the `/events` endpoint. For example, you may want to track events like: * `message:copied`: When a user copies a message * `message:code_snippet:copied`: When a user runs a code snippet from a message * `conversation:shared`: When a user shares a conversation When logged, they will be incorporated in the Inkeep Analytics reporting. The below shows an example of how to log these events. ```js log-event.js // Message event const eventData = { type: "message:copied", entityType: "message" // 'message' or 'conversation' messageId: "{message_id}", }; fetch('https://api.analytics.inkeep.com/events', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify(eventData) }) .then(response => response.json()) .then(data => console.log(data)); // Conversation Event const conversationEventData = { type: "conversation:shared", entityType: "conversation" conversationId: "{conversation_id}", }; fetch('https://api.analytics.inkeep.com/events', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify(conversationEventData) }) .then(response => response.json()) ``` # Delete Conversation delete /conversations/{id} # Get Conversation get /conversations/{id} # Get All Conversations get /conversations # Log Conversation post /conversations Logs a new conversation or updates an existing one with new messages. Always include all messages. **API Key Types:** `WEB`, `API` # Log Event post /events # Submit Feedback post /feedback # Analytics & Reporting Use Inkeep's analytics to keep tabs on usage and get actionable reporting. Prefer a visual walkthrough? Check out [this video](https://vimeo.com/1010449025/54aade446b?share=copy). ## Chat Sessions With the **Chat Sessions** table you get a granular view of all conversations users are having with your AI assistant. You can: * **View full conversations** - view all chats in full, including sources used and all messages. * **Review Thumbs Up / Thumbs down** - review conversations users gave feedback on. * **Use AI filters** - drill down on questions where there wasn't supporting content. * **Export as CSV** - download all question-answer pairs. ## Insights (AI Reports) In **Insights**, you get weekly and monthly reports that use AI to provide actionable insights for your documentation, support, and product teams. Each report includes concise summaries of: 1. **Prioritized themes** - top features or capabilities that users mention. 2. **Content gaps** - cases where Inkeep wasn't able to find supporting content. 3. **Feature gaps** - cases where a user asked for a feature that wasn't supported. 4. **Third-party mentions** - any third party software, frameworks, or services that users ask about in relation to your product. ## Analytics In **Analytics** you can see the key statistics about how users engage with your AI assistant. Metrics include: * **Total chat messages** * **Chat sessions** * **Unique users** * **Thumbs up vs. Thumbs down** * **Chat messages over time** * **Code snippets copied** * **Chat messages copied** * **Shared chats** * **New chat sessions** Want to correlate AI chat usage with your own user funnels or BI stack? Follow [this guide](/customization-guides/use-your-own-analytics). # Add user authentication Gate search or chat API calls with authentication. Sometimes, you may want to authenticate users accessing your search or chat service. This is typically applicable if you'd like to: * Restrict answers of the bot to content that a user has access to. Typically applicable for products with private, gated documentation. * Provide context (information) about the user or apply source filters in a way that cannot be tampered. * Implement your own CAPTCHA, throttling, or anti-bot security measures and use a JWT to verify that the user has passed it. ## Generate verification keys Generate a private and public key pair using `openssl` or the method of your choice. ```bash openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 openssl rsa -pubout -in private_key.pem -out public_key.pem ``` The *private* key will be used by your backend service to mint JWT tokens that are sent to the Inkeep search and chat service. It should not be shared with any other service or third party. ## Configure the integration In your Inkeep Integration, configure the following under **Advanced Settings**: * **Require user authentication**: set to `true` * **Public key**: provide the value of `public_key.pem` * **Issuer**: the value we should ensure matches the `iss` claim in the authentication token. Example: `https://auth.mydomain.com`. This is just a string URI so can be arbitrary. It just needs to uniquely identify your service. We will use the public key to validate the token that is provided to our service was created ('minted') by your service. We check the signature of the token and the `iss`, `exp`, `aud` and other standard claims. ## Generating a token in your backend You need a Microservice to create a JWT token every time a user is looking to make a search or chat. Here is an example of how to generate a token in Node.js: ```typescript import fs from "fs"; import jwt from "jsonwebtoken"; interface TokenConfiguration { iss: string; aud: string; integrationId: string; filters?: { // optional attributes: { $and: Array<{ [key: string]: string | { $in: string[] }; }>; }; }; userAttributes?: { // optional [key: string]: string | number | string[]; }; expirationOffsetHours?: number; } const generateToken = ({ iss, aud, integrationId, filters, userAttributes, expirationOffsetHours = 24, }: TokenConfiguration): string => { const privateKey = fs.readFileSync("private_key.pem", "utf8"); // your private key const payload = { iss, aud: "https://api.inkeep.com", integrationId, filters, userAttributes, exp: Math.floor(Date.now() / 1000) + expirationOffsetHours * 60 * 60, }; const token = jwt.sign(payload, privateKey, { algorithm: "RS256" }); return token; }; const exampleConfiguration: TokenConfiguration = { iss: "https://auth.mydomain.com", integrationId: "inkeep-integration-id", filters: { attributes: { $and: [ { env: "dev", }, { modules: { $in: ["module1", "module2"], }, }, ], }, }, userAttributes: { userId: "123456789", userEmail: "me@inkeep.com", userCohorts: [`group1`, `group2`], }, expirationOffsetHours: 24, }; const token = generateToken(exampleConfiguration); ``` The token should include the following claims: * `iss`: The issuer of the token. Example `https://auth.mydomain.com`. * `aud`: The audience of the token. It should always be `https://api.inkeep.com`. * `integrationId`: The ID of the integration. This must match the integration ID in the body of the request. * `filters` (optional): Same as documented in the [Start new chat](/inkeep-api/create-new-chat-session#chatfiltersinput) request body. Takes precedence over the `filters` value provided in the request body. * `userAttributes` (optional): Same as documented in the [Start new chat](/inkeep-api/create-new-chat-session#newsessionchatresultinput) request body. Takes precedence over the `userAttributes` value provided in the request body. * `exp`: The expiration time of the token, in seconds since the epoch. ## Add the token to chat or search requests ### When using the Inkeep widgets Pass in the token you mint to the widget configuration as `baseSettings.userToken`. See [here](/ui-components/common-settings/base). ### When using the Inkeep API Make sure to include these two headers: * `Authorization` : `Bearer {integrationApiKey}` (must still be present in all requests) * `User-Token`: `{JWT_TOKEN_SIGNED_BY_YOUR_BACKEND}` # Embedded Support Forms Use our AI annotations to intelligently show a contact support button and integrate with your support system. ## Scenario If the bot was not able to answer a user's question, you can optionally show a contact support button which can be configured to either open a customizable form or call a custom callback function. The form can be configured to collect whatever information you would like and you may optionally include information about the user's chat session which can then be turned into a support ticket. The callback function can be used to initiate a live chat. ## Example ```typescript import { type InkeepAIChatSettings, type FormConfig } from "@inkeep/uikit"; const supportFormConfig: FormConfig = { heading: "Contact support", fields: [ { type: "STANDARD_FIELD", label: "Name", name: "first_name", inputType: "TEXT", }, { type: "STANDARD_FIELD", label: "Email", name: "email", inputType: "EMAIL", required: true, }, { type: "INCLUDE_CHAT_SESSION", defaultValue: true, }, { type: "STANDARD_FIELD", label: "Additional details", name: "additional_details", inputType: "TEXTAREA", }, { type: 'STANDARD_FIELD', label: 'Category', name: 'category', inputType: 'SELECT', selectOptions: [ { label: 'Bug', value: 'BUG' }, { label: 'Feature idea', value: 'FEATURE' }, { label: 'Account access', value: 'ACCOUNT' }, ], }, ], submitCallback: (values) => { // replace with your own api call to create a support ticket return new Promise((resolve) => { setTimeout(() => { resolve({ success: true }); }, 2000); }); }, }; const inkeepAIChatSettings: InkeepAIChatSettings = { //... rest of inkeepAIChatSettings includeAIAnnotations: { shouldEscalateToSupport: true, }, aiAnnotationPolicies: { shouldEscalateToSupport: [ { threshold: "STANDARD", // "STRICT" or "STANDARD" action: { type: "SHOW_SUPPORT_BUTTON", label: "Contact Support", icon: { builtIn: "LuUsers" }, action: { type: "OPEN_FORM", formConfig: supportFormConfig, }, }, }, ], }, }; ``` ## IncludeAIAnnotations This is currently an enterprise preview feature. | Property | Type | Description | | ----------------------- | --------- | ------------------------------------------------------------------- | | shouldEscalateToSupport | `boolean` | Whether or not to include the `shouldEscalateToSupport` annotation. | ## AIAnnotationPolicies Be sure to include the corresponding property in `includeAIAnnotations`. | Property | Type | Description | | ----------------------- | --------------------------------- | --------------------------------------- | | shouldEscalateToSupport | `ShouldEscalateToSupportPolicy[]` | Policies for `shouldEscalateToSupport`. | ### ShouldEscalateToSupportPolicy | Property | Type | Description | | --------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | threshold | `"STANDARD"` \| `"STRICT"` | Support threshold. `"STRICT"` indicates that the bot is very confident about support escalation while `"STANDARD"` is lower threshold. | | action | `ShowEscalateToSupportButtonAction` | Action to take when support escalation threshold is met. | ### ShowEscalateToSupportButtonAction | Property | Type | Description | | -------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | | type | `"SHOW_SUPPORT_BUTTON"` | Type of action. | | label | `string` | Button label. | | icon | `CustomIcon` | Icon that goes next to the button label. See [here](/ui-components/common-settings/ai-chat#customicon) for details. | | action | `OpenFormAction` \| `InvokeCallbackAction` | Type of action to take when button is clicked. | #### OpenFormAction | Property | Type | Description | | ---------- | ------------- | --------------------------------------------------------------- | | type | `"OPEN_FORM"` | Type of action. | | formConfig | `FormConfig` | Custom form configuration. See [here](#formconfig) for details. | #### InvokeCallbackAction | Property | Type | Description | | ---------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------ | | type | `"INVOKE_CALLBACK"` | Type of action. | | callback | `(args: InvokeCallbackArgs) => void` | Callback to be invoked when the button is clicked. See [here](#invokecallbackargs) for details. | | shouldCloseModal | `boolean` | Determines whether the chat modal should close when the callback is invoked. Default is `false`. | #### InvokeCallbackArgs | Property | Type | Description | | ------------- | ----------- | ------------------------------- | | chatSessionId | `string` | Id for the chat session. | | messages | `Message[]` | Messages from the chat session. | # JavaScript Manage end-user usage tracking and privacy settings. In the example below we have a single checkbox where the user can opt out of all analytics. This pattern can be used more granularly to map your own customer preference system to ours, for example for cookie and tracking consent preferences. By default, Inkeep does not collect IP addresses, device IDs, or other device-based information from components of the Inkeep `uikit` library. ## Example JavaScript example: ```javascript // instantiate inkeepWidget using Inkeep.embed() // be sure to set any necessary initial values for optOutAnalyticalCookies, optOutAllAnalytics, optOutFunctionalCookies, remoteErrorLogsLevel if the user's preference is already known // ... const optOutAnalyticsInput = document.getElementById("optOutAnalytics"); // event listener for when user's preference changes optOutAnalyticsInput.addEventListener("change", (e) => { const shouldOptOut = e.target.checked; embeddedChat.render({ baseSettings: { optOutAnalyticalCookies: shouldOptOut, optOutAllAnalytics: shouldOptOut, optOutFunctionalCookies: shouldOptOut, remoteErrorLogsLevel: shouldOptOut ? 0 // None : 2, // Identifiable Errors }, }); }); ``` HTML example: ```html ``` # React Manage end-user usage tracking and privacy settings. In the example below we have a single checkbox where the user can opt out of all analytics. This pattern can be used more granularly to map your own customer preference system to ours. By default, Inkeep does not collect IP addresses, device IDs, or other device-based information from components of the Inkeep `uikit` library. ## Example Parent component: ```tsx import { ChangeEvent, useState } from "react"; import InkeepChatButton from "./components/InkeepChatButton"; import { RemoteErrorLogsLevel } from "@inkeep/uikit"; function ParentComponent() { const [optOutAnalytics, setOptOutAnalytics] = useState(false); const handleChangeCheckbox = (e: ChangeEvent) => { setOptOutAnalytics(e.target.checked); }; return ( <> {/* ... */} {/* ... */} ); } export default ParentComponent; ``` Widget component: ```tsx import { type InkeepChatButtonProps, type InkeepBaseSettings, type RemoteErrorLogsLevel, } from "@inkeep/uikit"; import { memo, useEffect, useState } from "react"; const baseSettings: InkeepBaseSettings = { apiKey: process.env.INKEEP_API_KEY!, integrationId: process.env.INKEEP_INTEGRATION_ID!, organizationId: process.env.INKEEP_ORGANIZATION_ID!, organizationDisplayName: "Inkeep", primaryBrandColor: "#26D6FF", }; interface InkeepPrivacyProperties { optOutAnalyticalCookies: boolean; optOutAllAnalytics: boolean; optOutFunctionalCookies: boolean; remoteErrorLogsLevel: RemoteErrorLogsLevel; } interface IProps { inkeepPrivacyProperties: InkeepPrivacyProperties; } function InkeepChatButton({ inkeepPrivacyProperties }: IProps) { const [ChatButton, setChatButton] = useState>(); 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: { ...baseSettings, ...inkeepPrivacyProperties, }, aiChatSettings: { // optional settings }, searchSettings: { // optional settings }, modalSettings: { // optional settings }, }; return ChatButton ? : <>; } // use memo to avoid component re-rendering export default memo(InkeepChatButton); ``` # In-App Copilots In-App Copilots provide intelligent, context-aware assistance directly within your application, enhancing user experience and streamlining interactions. Use [context and guidance](/customization-guides/product-workflows#context-and-guidance) to provide dynamic user or product specific information or [workflows](/customization-guides/product-workflows#workflows) to guide users through complex tasks or common scenarios. ## Context and guidance Context and guidance can be passed in via the [aiChatSettings](/ui-components/common-settings/ai-chat). ## Workflows Workflows allow you to create app-specific flows that are specific to common scenarios your users may face. This often maps to specific tasks in your onboarding or user activation funnel. Workflows support: * A custom initial message * Specifying clarifying questions or information the bot should ask for * Custom attachment types that can invoke forms, modals, or API calls within your front-end application to gather user-specific information ### Example ```ts example.ts import { type Workflow } from "@inkeep/uikit"; import { type InkeepAIChatSettings } from "@inkeep/uikit"; const integrateWithMyAppWorkflow: Workflow = { id: "ikp_integrate_with_my_app", displayName: "Help me integrate with my app", goals: [ "Identify the platform that the user is looking to add Inkeep on. Can be a website-based platform or docs platform, Slack/Discord bots, or via API", "Give instructions for how to add the bot to that platform." ], botPersona: "You are an expert solutions engineer", informationToCollect: [ { description: "Platform they are integrating Inkeep to", required: true, }, ], guidance: [ "If not clear from the user's message, ask clarifying questions to understand the underlying platform (e.g. Docusaurus vs Slack vs ReadMe, etc.).", "If the platform is not detailed in our quickstarts, then ask whether it's a React or JavaScript based platform so you can give the right guidance based on that.", ], initialReplyMessage: "Happy to help. \n Where are you looking to add Inkeep?", }; //...rest of your settings const aiChatSettings: InkeepAIChatSettings = { //... rest of aiChatSettings workflows: [integrateWithMyAppWorkflow] }; ``` ## Workflow | Property | Type | Description | | -------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | id | `string` | **Required**. Id of the workflow. | | displayName | `string` | **Required**. Label of workflow in the UI. | | goals | `string[]` | **Required**. Goals for the workflow, not visible to the user. | | informationToCollect | `WorkflowInformationToCollect[]` | **Required**. Information to collect from the user. [Learn more](/customization-guides/product-workflows#workflowinformationtocollect). | | botPersona | `string` | The persona of the bot useful for defining the character, tone, and interaction style the bot will adopt when communicating with users. | | context | `string[]` | Additional context about this workflow for the LLM, not visible to the user. | | guidance | `string[]` | Additional guidance for the LLM, not visible to the user. | | initialReplyMessage | `string` | **Required**. The reply message from the bot after the user selects a workflow. | | supportedInputs | `(WorkflowModalSingleInput \| WorkflowFunctionalMultiInput)[]` | Configuration for the workflow inputs. [Learn more](/customization-guides/product-workflows#workflowmodalsingleinput). | ### WorkflowInformationToCollect | Property | Type | Description | | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------ | | description | `string` | **Required**. Description of the information to be collected from the user in order to assist them through the workflow. | | required | `boolean` | **Required**. Whether or not this information is required. | ## Attachments - Built In Modal ```ts example.ts import { type Workflow, type WorkflowModalSingleInput } from "@inkeep/uikit"; const exampleWorkflow: Workflow = { id: "example_workflow", displayName: "Share Error Log", goals: [ "Collect error log information from the user", "Provide guidance based on the error log content" ], botPersona: "You are a helpful technical support assistant", informationToCollect: [ { description: "Error log details", required: true, }, ], initialReplyMessage: "I'd be happy to help you with your error. Please share your error log using the button below.", supportedInputs: [{ id: "error_log_input", type: "MODAL", displayName: "Share Error Log", contentType: { type: "CODE", contentInputLabel: "Error Log", language: "plaintext", }, workflowModalProps: { titleInputLabel: "Error Description", modalHelpText: "Please paste your error log and provide a brief description of when this error occurred.", }, } as WorkflowModalSingleInput], }; ``` ### WorkflowModalSingleInput This input type will open a **built-in modal** with a form based on the configuration below to collect the user's information. | Property | Type | Description | | ------------------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | | id | `string` | **Required**. Id of the input. | | type | `"MODAL"` | **Required**. | | displayName | `string` | **Required**. Button label in the UI that opens the modal. | | contentType | `WorkflowCodeContentType` \| `WorkflowTextContentType` | **Required**. Type of content user is inputting. [Learn more](/customization-guides/product-workflows#workflowcodecontenttype). | | workflowModalProps | `WorkflowModalProps` | Additional modal configuration. [Learn more](/customization-guides/product-workflows#workflowmodalprops). | ### WorkflowModalProps | Property | Type | Description | | ---------------- | -------------- | ------------------------------------------------- | | titleInputLabel | `string` | Label for the title input. Defaults to `"Title"`. | | modalHelpText | `string` | Help text shown in the modal. | | modalHelpElement | `ReactElement` | Help element shown in the modal. | ### WorkflowCodeContentType | Property | Type | Description | | ----------------- | ------------ | --------------------------------------------------------------------------------------------------------------- | | type | `"CODE"` | **Required**. | | contentInputLabel | `string` | **Required**. Label for the content input, also provided to the LLM when chat is submitted. | | attachmentIcon | `CustomIcon` | Icon next to the title in the attachment item. [Learn more](/customization-guides/style-components#customicon). | | language | `string` | Code language. | ### WorkflowTextContentType | Property | Type | Description | | ----------------- | ------------ | --------------------------------------------------------------------------------------------------------------- | | type | `"TEXT"` | **Required**. | | contentInputLabel | `string` | **Required**. Label for the content input, also provided to the LLM when chat is submitted. | | attachmentIcon | `CustomIcon` | Icon next to the title in the attachment item. [Learn more](/customization-guides/style-components#customicon). | ### MessageAttachment | Property | Type | Description | | ----------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------- | | contentType | `WorkflowCodeContentType` \| `WorkflowTextContentType` | **Required**. [Learn more](/customization-guides/product-workflows#workflowcodecontenttype). | | title | `string` | **Required**. Attachment title. | | content | `string` | **Required**. Attachment content. | | id | `string` | **Required**. Attachment id. | | context | `string[]` | Additional attachment context. | ## Attachments - In-app callback ```ts example.ts import { type Workflow, type WorkflowFunctionalMultiInput, type MessageAttachment } from "@inkeep/uikit"; const troubleshootErrorWorkflow: Workflow = { id: "troubleshoot_error_workflow", displayName: "Troubleshoot Error", goals: ["Collect error information", "Provide initial troubleshooting steps"], botPersona: "You are an expert technical support engineer", informationToCollect: [{ description: "Error details", required: true }], initialReplyMessage: "I'm here to help troubleshoot. Let's gather some information about the error.", supportedInputs: [{ id: "fetch_error_data", type: "FUNCTIONAL_MULTI_ATTACHMENT", displayName: "Fetch Error Info", onInvoke: ({ callback, currentMessageAttachments }) => { // either call your API or trigger an in-app modal or other UI setTimeout(() => { const errorLogAttachment: MessageAttachment = { id: "error_log_attachment", title: "Error Log", content: "Error: Unable to connect to database\nTimestamp: 2023-04-15T14:30:00Z", contentType: { type: "CODE", contentInputLabel: "Error Log", language: "plaintext" } }; // pass information back to the workflow callback([...currentMessageAttachments, errorLogAttachment]); }, 1000); }, } as WorkflowFunctionalMultiInput], }; ``` ### WorkflowFunctionalMultiInput This input type allows you to provide your own custom logic to pass information back to the chatbot as an attachment. This custom logic might be your own form, modal, or even via APIs that fetch information about a user from your own downstream services. | Property | Type | Description | | ----------- | ----------------------------------- | --------------------------------------------------------------------------- | | id | `string` | **Required**. Id of the input. | | type | `"FUNCTIONAL_MULTI_ATTACHMENT"` | **Required**. | | displayName | `string` | **Required**. Button label in the UI that triggers the `onInvoke` function. | | onInvoke | `(OnInvokeArgs) => void` See below. | **Required**. Function that runs when the button is clicked. | #### OnInvokeArgs `onInvoke` is called with the following arguments: | Property | Type | Description | | ------------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | workflow | `Workflow` | The workflow the user has selected. | | selectedInputType | `WorkflowModalSingleInput` \| `WorkflowFunctionalMultiInput[]` | The input type the user has selected. | | callback | `(messageAttachments: MessageAttachment[]) => void` | Function to update the message attachments. This will override what is currently in state so append any new attachments to `currentMessageAttachments` to avoid removing an attachment. [Learn more](/customization-guides/product-workflows#messageattachment). | | currentMessageAttachments | `MessageAttachment[]` | The message attachments that user attached. [Learn more](/customization-guides/product-workflows#messageattachment). | # Sharable chats Enable the **Share** button in your Inkeep components. When clicked, it generates a link to a chat so a user can share with other users. We first need to create a page where the share links will lead to (e.g. `https://inkeep.com/ask-ai`). This should be a page in your documentation, app, or marketing site that contains the Inkeep Embedded Chat component. Reference: * [React](/ui-components/react/embedded-chat) * [JavaScript](/ui-components/js-snippet/embedded-chat) Examples: * [Next.js](/integrations/nextjs/embedded-chat) * [Webflow](/integrations/webflow/embedded-chat) The Embedded Chat automatically displays any conversation based on if a `?chatId=` query parameter is present in the URL of the loading the page it's embedded in. Set the value of `aiChatSettings.shareChatUrlBasePath` to the URL from the previous step for all Inkeep components you want to enable the **Share** button for. This can include any Search Bar, Chat Button, or Custom Trigger you use in any site or application. When clicked, it'll copy a link to the user's clipboard in the format of `https://{shareChatUrlBasePath}?chatId={sharedChatId}`. Also set `aiChatSettings.isChatSharingEnabled` to `true`. ## Example ```typescript import { type InkeepAIChatSettings } from "@inkeep/uikit"; const inkeepAIChatSettings: InkeepAIChatSettings = { //... rest of inkeepAIChatSettings isChatSharingEnabled: true, shareChatUrlBasePath: "https://mydomain.com/ask-ai", }; ``` # Style the components Customize the search and chat widgets to match your brand. ## The `theme` object The `theme` object is part of the `baseSettings` and can be used to customize the widgets to fit your brand's style. It allows you to overrides variables that we use throughout our styling. ### Fonts Specify which fonts should be applied to the body, headings, or code (mono) elements. For example: ```javascript //... baseSettings: { //... theme: { tokens: { fonts: { heading: "'Space Grotesk'", body: "'Inter'", mono: "'Space Mono', monospace", }, }, } } ``` ### Font sizes Customize the font sizes that are used throughout the widget. Below are the default font size tokens. ```javascript //... baseSettings: { //... theme: { tokens: { fontSizes: { '3xs': '0.45rem', '2xs': '0.625rem', xs: '0.75rem', sm: '0.875rem', md: '1rem', lg: '1.125rem', xl: '1.25rem', '2xl': '1.5rem', '3xl': '1.875rem', '4xl': '2.25rem', '5xl': '3rem', '6xl': '3.75rem', '7xl': '4.5rem', '8xl': '6rem', '9xl': '8rem', }, }, } } ``` ### Colors The widget ships with a neutral gray scheme and derives accent colors from the `primaryBrandColor` (passed into `baseSettings`), but these colors can be overridden with your own colors if you wish. Here is an example: ```javascript //... baseSettings: { //... theme: { tokens: { colors: { // grays for light mode 'gray.50': '#f8f9fa', 'gray.100': '#f1f3f5', 'gray.200': '#eceef0', 'gray.300': '#e6e8eb', 'gray.400': '#dfe3e6', 'gray.500': '#BDC2C7', 'gray.600': '#889096', 'gray.700': '#687076', 'gray.800': '#36424A', 'gray.900': '#11181c', // grays for dark mode 'grayDark.50': '#ededed', 'grayDark.100': '#a0a0a0', 'grayDark.200': '#7e7e7e', 'grayDark.300': '#707070', 'grayDark.400': '#505050', 'grayDark.500': '#3e3e3e', 'grayDark.600': '#343434', 'grayDark.700': '#2e2e2e', 'grayDark.800': '#282828', 'grayDark.900': '#1c1c1c', }, }, primaryColors: { // by default, derived from primaryBrandColor textColorOnPrimary: '#11181c', textBold: '#141d20', textSubtle: '#354a51', lighter: '#e5feff', light: '#85f0ff', lightSubtle: '#f1f8fa', medium: '#26d6ff', // primaryBrandColor strong: '#00b5dd', stronger: '#006881', hitContentPreview: '#00b5dd', hitContentPreviewHover: '#006881', textColorOnPrimary: 'white', }, } } ``` ### Z-indices Customizing the zIndex tokens is usually not necessary but can be helpful if there is an element on your site with a high z-index that is being rendered on top of one of the widgets or modal. Below are the default token values. It is a good idea to update the rest of z-index tokens relative to the one you need to change, for example if changing `modal` to `2000` make sure to also bump `popover` to `2100`, `skipLink` to `2200` etc. ```javascript //... baseSettings: { //... theme: { tokens: { zIndex: { hide: -1, auto: 'auto', base: 0, docked: 10, dropdown: 1000, sticky: 1100, banner: 1200, overlay: 1300, modal: 1400, popover: 1500, skipLink: 1600, toast: 1700, tooltip: 1800, }, } } } ``` ### Search Bar The Search Bar component has two variants to chose from `subtle` and `emphasized`, the default is `subtle`. There are also four size variants to chose from `expand`, `compact`, `shrink`, or `medium`, the default is `compact`. The [Docusaurus package](/integrations/docusaurus) defaults to `shrink`. To change the variant and size: ```javascript //... baseSettings: { //... theme: { components: { SearchBarTrigger: { defaultProps: { size: 'expand', // 'expand' 'compact' 'shrink' 'medium' variant: 'emphasized', // 'emphasized' 'subtle' }, }, } } } ``` ### Embedded Chat #### Theme variants The Embedded Chat component has two styling variants: `container-with-shadow` and `no-shadow`, the default is `container-with-shadow`. There are also four size variants: `shrink-vertically`, `expand`, `default`, or `full-viewport`, the default is `default`. To change the variant and size: ```javascript //... baseSettings: { //... theme: { components: { AIChatPageWrapper: { defaultProps: { size: 'shrink-vertically', // 'shrink-vertically' 'expand', 'default', 'full-viewport' variant: 'no-shadow', // 'no-shadow' or 'container-with-shadow' }, }, } } } ``` #### Centering and sizing The embedded chat is sized relative to it's container. By default, it expands to a certain max width and heights that preserve a reasonable aspect ratio for different breakpoints and tries to center itself relative to its parent. So it's properly centered, consider wrapping it in parents that have the below stylings. ##### With a `display='flex'` parent
```jsx React
``` ```html HTML
```

##### With a `display='block'` parent
```jsx React
``` ```html HTML
```

##### Customizing dimensions (using `size='expand'`)
```jsx React
``` ```html HTML
```
We recommend setting the height for the container div to `height: min(80vh, 800px);` ## Syntax highlighting theme By default, the Prism syntax highlighting theme we use is [oneLight](https://github.com/FormidableLabs/prism-react-renderer/blob/master/packages/prism-react-renderer/src/themes/oneLight.ts) in light mode and [vsDark](https://github.com/FormidableLabs/prism-react-renderer/blob/master/packages/prism-react-renderer/src/themes/vsDark.ts) in dark mode. To use a different theme, you can provide it in the `theme.syntaxHighlighter` prop. For example, to use the `dracula` theme: ```javascript import { themes } from "prism-react-renderer"; const inkeepProps = { theme: { syntaxHighlighter: { lightTheme: themes.dracula, // theme used in light mode darkTheme: themes.dracula, // theme used in dark mode }, }, }; ``` You can use published Prism themes or [create your own](https://github.com/PrismJS/prism-themes). ## Dark mode ### Controlling the color mode By default, the widgets will be rendered in light mode, to change the color mode use the `forcedColorMode` prop in the `colorMode` object of the `baseSettings`. ```javascript //.. baseSettings: { // ... colorMode: { forcedColorMode: 'dark', // options: 'light' or dark' } } ``` ### System color mode To use the user's system preference color mode, set the `enableSystem` prop to `true` (by default it is `false`) in the `colorMode` object of `baseSettings`. ```javascript baseSettings: { //... colorMode: { enableSystem: true, } } ``` ## Customize the icons ## Custom CSS and styling Additional customization can be done via CSS stylesheets. Because the Inkeep widgets are isolated via a shadow DOM, any custom styling must be passed to the widget configuration in order for them to be applied. ## InkeepStyleSettings | Property | Type | Description | | -------------- | ---------------- | --------------------------------------------------------- | | stylesheetUrls | `string[]` | Array of URLs to stylesheets with style overrides. | | stylesheets | `ReactElement[]` | Array of `` or `; const inkeepEmbeddedChatProps: InkeepEmbeddedChatProps = { baseSettings: { // ... typeof InkeepBaseSettings theme: { stylesheets: [styleTag], }, }, //... }; ``` ### Hosting the stylesheet Please ensure that stylesheets are in valid, **publicly accessible** URLs and correctly load. If a stylesheet does not load, the Inkeep widgets will not render.{" "} You can host your stylesheet in a CDN or include it as part of the public assets exposed in an existing application. For example: If using [Next.js](https://nextjs.org/docs), you can create a css file within the [public folder](https://nextjs.org/docs/app/building-your-application/optimizing/static-assets) and then pass that url to the `stylesheetUrls` prop. For example: ```javascript // for this example there is a file in the public folder is called inkeep.css const inkeepEmbeddedChatProps: InkeepEmbeddedChatProps = { baseSettings: { // ... typeof InkeepBaseSettings theme: { stylesheetUrls: ["/inkeep.css"], }, }, //... }; ``` #### Gatsby If using [Gatsby](https://www.gatsbyjs.com/), you can create a static folder in the root of your project, then create a css file for the Inkeep widget style overrides, this will then get copied to the [public folder](https://www.gatsbyjs.com/docs/how-to/images-and-media/static-folder/), then you can pass the url to the `stylesheetUrls` prop. For example: ```javascript // for this example there is a file in the static folder is called inkeep.css const inkeepEmbeddedChatProps: InkeepEmbeddedChatProps = { baseSettings: { // ... typeof InkeepBaseSettings theme: { stylesheetUrls: ["/inkeep.css"], }, }, //... }; ``` ### Using Inkeep CSS variables Inkeep CSS variables can be utilized within stylesheets to customize elements. For example, the Chat Button uses a dark gray color by default. You can use the `.ikp-floating-button` and any of the pre-defined theme color tokens or any color of your choice to customize it: ```css .ikp-floating-button { background: var(--ikp-colors-inkeep-primary-medium); } ``` ### Dark mode style overrides To apply a specific style for dark mode only, utilize the `[data-theme='dark']` attribute. For example: ```css [data-theme="dark"] .ikp-floating-button { background: #353e52; } ``` # Add your own analytics Use our callback function to log events from the search and chat widgets to your own analytics tool. ## Scenario To understand how usage of the Inkeep search or chat affects your business, you may want to log events from the search and chat widgets to your own analytics tool. This could be Mixpanel, Posthog, Amplitude, Segment or other analytics or CDP tools. We expose all the events that we log to our own analytics suite through a callback function called `logEventCallback` that can be configured in the [`baseSettings`](/ui-components/common-settings/base) configuration. Below is an example of how to use `logEventCallback` to log events related to the `chat_message_bot_response_received` event to your analytics tool. You can check out the full reference of events by inspecting the `InkeepCallbackEvent` type in the npm package. We recommend you only log the events and properties on the events that you find relevant. ## Example ```typescript // customAnalyticsCallback.ts import { InkeepCallbackEvent } from '@inkeep/uikit'; const customAnalyticsCallback = (event: InkeepCallbackEvent): void => { // Check if the event type is 'chat_message_bot_response_received' if (event.eventName === 'chat_message_bot_response_received') { const { question, responseMessage } = event.properties; // Log to your own analytics tool or CDP. console.log('Question: ', question); console.log('Response: ', responseMessage.content); } }; export customAnalyticsCallback; ``` ## Available events Accessible via the `eventName` property on the `InkeepCallbackEvent` type. | Event name | Description | | --------------------------------------- | ----------------------------------------------------------------------------- | | chat\_message\_submitted | User submits a chat message | | chat\_message\_bot\_response\_received | Bot response to user message is received | | search\_query\_submitted | Search query is submitted | | search\_query\_response\_received | Response to search query is received | | search\_result\_clicked | Search result is clicked | | chat\_thumbs\_up\_feedback\_submitted | User submits thumbs up feedback | | chat\_thumbs\_down\_feedback\_submitted | User submits thumbs down feedback | | chat\_history\_cleared | Chat history is cleared | | chat\_share\_button\_clicked | Share button is clicked | | chat\_message\_copied | Chat message is copied | | chat\_code\_block\_copied | Code block in chat is copied | | chat\_response\_citation\_clicked | Citation link is clicked | | get\_help\_option\_clicked | Get help item is clicked | | support\_button\_clicked | Support button (if configured as part of the aiAnnotationPolicies) is clicked | {/* | search_result_clicked | search result is clicked | */} {/* | search_result_upvote | search result is upvoted | */} {/* | search_result_downvote | search result is downvoted | */} {/* | call_to_action_clicked | call to action is clicked | */} {/* | chat_revised_answer_submitted | user submits revised answer | */} {/* | chat_response_citation_clicked | citation in response is clicked | */} {/* | chat_bot_had_insufficient_knowledge | bot had insufficient knowledge to answer | */} {/* | modal_mode_switched | modal mode is switched | */} {/* | chat_shared_session_loaded | loading a shared chat session | */} {/* | chat_shared_session_continued | continuing a shared chat session | */} {/* | chat_widget_loaded | chat widget finishes loading | */} {/* | shared_chat_loaded | shared chat is loaded | */} {/* | modal_opened | modal is opened | */} {/* | modal_closed | modal is closed | */} ## Common properties The below are shared by all events. | Property name | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------- | | organization\_id | Organization ID | | widgetLibraryVersion | Widget library version | | interactionType | Type of interaction. Value can be one of `'EMBEDDED_CHAT' \| 'CHAT_BUTTON' \| 'CUSTOM_TRIGGER' \| 'SEARCH_BAR'`. | {/* | product | Product associated with interaction, if provided to the React component. | */} {/* | is_modal_mode_switching_enabled | Whether modal mode switching is enabled | */} {/* | chat_mode_component_default | Default chat mode | */} {/* | is_chat_mode_toggle_enabled | Whether chat mode toggle is enabled | */} {/* | shared_chat_session_id | ID of shared chat session | */} {/* | integration_id | Integration ID | */} ### User props If provided to the React component, the below user properties are available. | Property name | Description | | ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | | userIdentificationType | How user is identified. Can be of values `'ANONYMOUS' \| 'COOKIED' \| 'ID_PROVIDED'` depending on analytics configuration. | | userCohorts | User cohorts | | userEmail | User email | {/* | user_type | Type of user | */} ### Chat properties The below are shared by all chat events. | Property name | Description | | ------------------------- | ---------------------------- | | chatModeCurrentlySelected | Currently selected chat mode | | chatSessionId | ID of shared chat session | # Comparisons How does Inkeep compare to other AI search and chat services? ## TL;DR Teams that have evaluated our chat quality with other products have generally favored Inkeep because our AI: * admits when it doesn't know and guides users to support channels * contains minimal (if any) hallucinations or search misses * provides rich citations that help users inspect answers * leverages many sources while prioritizing authoritative content That said - don't take our word for it! Use our [free trial](https://inkeep.com/demo-form) to test out Inkeep with your toughest questions. ## Comparisons Teams typically look for a managed solution so that they don't have to invest engineering resources in: * Building the pipelines and infra to ingest and keep content up to date * Experimenting with LLM models and RAG techniques to get high-quality results * Building user-friendly chat UIs that are embeddable anywhere * Providing insights to product and documentation teams Algolia is a popular service that is particularly focused on just search. While Algolia offers "Neural" or "AI search", those terms refer to search algorithms rather than "AI chat" or conversational experiences. While Inkeep similarly provides a neural search service (with unlimited usage in any plan), we also provide: * AI chat experiences powered by large language models and your own content * An analytics platform to analyze user conversations - not just search queries. * Slack and Discord bots that can be used in your employee or customer-facing channels * Zero-effort ingestion of any website using our intelligent scraper (no need for managing content over APIs!) Many teams use Inkeep for just the chat experiences - you don't have to replace your existing Algolia search experience if you're happy with it. Intercom is a general support solution for any type of product - consumer-facing, B2B SaaS products, etc. Their offering includes "Fin", an AI chatbot focused on support deflection. In comparison to Intercom, Inkeep shines for: * UX experiences tailored for developer-facing or technical products * Being easy to embed in any touch point - Slack, Discord, Search Bar, Chat button, etc. * Zero-effort ingestion of any content source * A focus on feedback loops for technical documentation teams * Affordable pricing Kapa.ai is a startup focused on AI chat for developer products. While Inkeep and Kapa.ai share similarities, Inkeep includes: * AI generated reports that provide actionable summaries of documentation gaps * Inline citations (as footnotes \[1]) in chat answers * A [Search Bar](/ui-components/react/search-bar) component for rich search experiences * An [Embedded chat](/ui-components/react/embedded-chat) component for help centers * Built-in experience for [creating support tickets](/customization-guides/embedded-support-form) with your support system * UI components that are highly configurable and themable, support dark and light modes, and are available as both React components as well as JavaScript snippets for integration in any. * A fully conversational copilot for support teams available in any support platform. Mendable is a startup focused on AI chat for sales and support. While Inkeep and Mendable share similarities, Inkeep includes: * AI generated reports that provide precise summaries of documentation gaps * Inline citations (as footnotes \[1]) in chat answers * Built-in experience for [creating support tickets](/customization-guides/embedded-support-form) with your support system OpenAI's Assistant's API helps companies build "agentic" workflows, where an assistant can use a variety of tools and OpenAI maintains chat history and state of usage of those tools. One of those tools is "retrieval", which can perform RAG on pre-uploaded documents. It's up to the user to programmatically fetch those documents, format them, and upload them and build out a UI and their own logic. Inkeep's AI chat service, by comparison, is meant to be an end-to-end offering that requires zero engineering effort while providing high-quality results. Beyond abstracting away retrieval, Inkeep's service also provides: * Out-of-box connectors for ingesting and syncing content from Websites, Slack, GitHub, etc. * UI components that are easily embeddable in any website * A search API and UI components that can replace traditional search services like Algolia * Analytics for your team on documentation gaps and trending questions * High availability and resiliency against outages with one LLM provider SiteGPT and Chatbase are customer support AI chatbots tailored for traditional consumer or B2B SaaS solutions. While Inkeep shares some similarities, customers who've compared or migrated from these platforms have generally found Inkeep to have answers with significantly less hallucinations and better RAG performance. Inkeep is also more geared for technical B2B SaaS or developer tool products. We aim to make our service accessible to companies of all sizes and needs while providing best-in-class responses and quality. **Inkeep offers unlimited questions** in any plan. For comparisons to Inkeep's pricing, evaluate estimated usage against a company's pricing for state-of-the-art models like OpenAI GPT-4o or Claude Sonnet 3.5 models and not lighter-weight models like OpenAI GPT-3.5 or Claude Haiku. We suggest teams evaluate solutions based for high quality responses and experiences that match your user's expectations. # Features Can't find an answer? Reach out to [support@inkeep.com](mailto:support@inkeep.com?subject=Question). Yes, our AI assistant should automatically answer in the language that the user used. You can also set a 'primary language' in your project settings if your users or documentation are primarily in a different language. This will bias our AI and content ingestion service to account for that language (but still work for any). You can also customize the strings or use an internationalization solution with Inkeep's UI components Yes. This feature is currently in private preview, please reach out for access. Using this feature allows you to use Inkeep with any library or SDK that is OpenAI compatible. Currently Inkeep is not setup to be a self-hostable solution. However, if your concern is data processing or privacy concerns, review our [privacy overview](https://docs.inkeep.com/overview/privacy) to learn about options available for customers with custom data processing requirements. While we love supporting open source teams and projects like Drizzle, Auth.js and others, our solution is not currently open source. For security reasons, our UI components don't render images or content from URLs. We typically don't ingest images out of the box, but let us know if images are an important part of your documentation. We are able to ingest them if needed. Because YouTube transcriptions are not high quality and often only make sense in relation to the graphics in the videos, we generally don't recommend using raw videos as a source. However, let us know if this is important for you, we have done it in the past for some customers. # Projects & Sources Can't find an answer? Reach out to [support@inkeep.com](mailto:support@inkeep.com?subject=Question). A source is a collection of content, often deliminated by sitemaps, url paths, or other logical units. For example, a GitHub repository is "1" source. A marketing site is another. A documentation site can be one or split into multiple. We consider a source seperate if it's crawled independently of others. Projects are often used to represent different core scenarios or sets of sources. For example, teams sometimes create different projects for different product lines or to separate out internal (employee) facing scenarios that use private content sources compared to a customer-facing project with public sources. Integrations represent different "instances" or locations of a chatbot for your project. For example, you may register one integration for your marketing site, one for your documentation site, and another for your help desk. Our analytics views are filterable by integration - allowing you to segment usage patterns. Each integration gets its own API key. All project settings and sources assigned to that project apply to an integration. While it is up to your company's own policies and best practices, chat and search API keys are generally ok to be included in client-side (browser) code if the search and chat service is intended for public, unauthenticated use. This is typical of public-facing documentation sites or marketing pages. We implement various protection mechanisms to prevent against abusive usage and in general the chat service is scoped to answering questions about your product so is not a general use API key like an OpenAI or general LLM provider API key would be. If you would like to implement your own protection mechanisms, it is possible to have traffic routed through a private proxy, implement CAPTCHA, and/or add user authentication. Teams typically create an Integration within a project they use for staging environments. This can help separate out usage from your production usage. If you need truly independent staging or testing environments, you can set up two projects that mirror each other instead. Separately, if using our UI components, you can set `baseSettings`.`env` to `DEVELOPMENT` so that questions asked in that mode are not shown in the analytics dashboard. # Pricing Can't find an answer? Reach out to [support@inkeep.com](mailto:support@inkeep.com?subject=Question). We aim to send a sandbox in under an hour for most small to medium websites. If a site is very large or we detect that it needs human review, it may take more than an hour. We try to share the demo as quickly as possible while maintaining quality, so please [reach out](mailto:help@inkeep.com) if you have any urgent questions or don't hear back from us within a day. We aim to account for your expected usage in the pricing we quote you. If you're using a self-serve plan and your usage exceeds typical usage patterns, we'll communicate with your team to find pricing that makes sense for your scenario. Note that we generally We monitor usage patterns to ensure fair use and maintain service quality for all customers. If we notice unusually high or potentially abusive usage, we may reach out to discuss your needs and explore solutions. To get a free demo on your content, just fill out this form [here](https://inkeep.com/demo-form?cta_id=nav_demo_cta). You'll get a personalized sandbox over email, or you can schedule a call with our team for a full product demo. A sandbox is just a sharable link where you can try our UI components and chat service against your own content without creating an account or providing a payment method. For self-serve customers, we offer a 30-day free trial. You can use that time to use our Slack bot in your internal support channels, launch publicly, or otherwise test Inkeep to your liking. If you have custom or complex requirements or would like direct ongoing support from our team, we generally offer a 30 day pilot at a flat price with unlimited usage. We work with you during this period to integrate Inkeep wherever you'd like to try it, measure usage, and work through any implementation details or requirements that come up. A "search" refers to a traditional search query -- a user enters a few words or a phrase, and Inkeep returns a list of relevant documents. Our search service is available if you (optionally) use our Search Bar component or via our API. Unlimited searches are included in any plan. Our AI search is powered by proprietary neural net and lexical search algorithms that deliver fast, highly relevant out of the box search results without any tuning. Our AI search is part of our "RAG" system we use to feed relevant content to to large language models to power our "Ask AI" chat experience. We use a variety of mechanisms to protect our service against malicious or miscellaneous usage. For example, we set dynamic IP-level or organization-level throttling to prevent against volume-based attacks. Further, we smartly detect if there is a surge of questions that seem out of the ordinary from your company's typically usage patterns. We strive to mitigate against these attack vectors and have not yet had downtime or cost issues related to malicious usage. If you're particularly keen on enforcing your protection mechanisms, we can work with you to [add authentication](/customization-guides/authenticate-users), add a CAPTCHA service, send usage through your own proxy, or in general find a solution that meets your requirements. By default, the terms of all of our plans ask that customers keep the Inkeep branding in our UI components. If you require a white labeled solution, it can be offered as an add-on to enterprise plans. For Open Source projects that don't have a monetized backing entity, we typically do offer free usage. This can be as a Discord bot, Slack bot, or UI components for documentation. Please reach out to our team for details. We do not currently support multi-tenant or reseller/vendor scenarios. # Troubleshooting Can't find an answer? Reach out to [support@inkeep.com](mailto:support@inkeep.com?subject=Question). If you're having trouble loading the Inkeep UI components, try checking the below. Inkeep components are loaded into a "Shadow" DOM. This is done so that they don't affect your pages styles or vice versa. Use your browser's developer tools to check if there's an HTML element with an ID that contains `inkeep-shadow`. If you don't see an element that matches that description, that means there is an issue instantiating the component. This is typically due to an issue with loading the JavaScript. If you're using the `uikit-js` package, there are two `scripts` that need to be loaded: 1. The component library: `` 2. A script that includes `Inkeep.embed({...})` to instantiate the UI components. Inspect your DOM to see if both of these scripts were successfully loaded. Sometimes, the issue is that script #2 loads before script #1. To prevent this, you can add an id to script #1 and use the "load" event to execute script #2. ``` inkeepScript.addEventListener("load", function () { // embed logic }); ``` The UI components are designed to not display until custom stylesheets have been fetched. If you specify a `stylesheetUrls` property in the widget configurations but the provided path is invalid, this can cause the widget to not render. Please ensure that the URL is an absolute URL (like for a CDN); or, if using a relative URL, that that relative file is indeed bundled and published publicly. This typically requires putting the CSS file in a `public` or `static` folder, depending on the framework. If you see the `inkeep-shadow` element, the issue might be that the Search Bar or Chat Button components are not being properly displayed in your page. Find all the elements with id `inkeep-widget-root` and inspect whether they are being rendered off the page or hidden by other elements. By default, UI components use same-domain cookies in the browser to keep track of what "mode" a user last had open so that they are taken to that same mode when they next open the Inkeep Modal. To disable this "remember" behavior, you can set `baseSettings.optOutFunctionalCookies` = `true` or use the `forceInitialDefaultView` prop in `modalSettings` which will open the modal in the `defaultView` regardless of which view was last opened. New users will see the default search or chat experience based on your component type or if you customize `modalSettings.defaultView`. The Inkeep UI components and related scripts are loaded asynchronously so they don't affect page performance. # Add AI Chat to your Astro app ### Define the component Next, create an `InkeepChatButton.tsx` file for the [`Chat Button`](https://docs.inkeep.com/ui-components/react/chat-button) component. ### Use the component Now use the `InkeepChatButton.tsx` component in your Astro based website. ```jsx // ... // ... ``` Use the `client:load` directive to load and hydrate the `InkeepChatButton.tsx` component immediately on page load. # Add AI search to your Astro app ### Define the component Next, create an `InkeepSearchBar.tsx` file for the [`Search Bar`](https://docs.inkeep.com/ui-components/react/search-bar) component. ### Use the component Now use the `InkeepSearchBar.tsx` component in your Astro based website. ```jsx // ... // ... ``` Use the `client:load` directive to load and hydrate the `InkeepSearchBar.tsx` component immediately on page load. # Add AI Chat to your Bettermode community ## Overview You can add AI chat as a [Bettermode](https://bettermode.com/) widget to your Bettermode community. ## Create App To use the AI chat, first we need to create an application on the Bettermode platform. 1. Open the Bettermode [Developer Portal](https://developers.bettermode.com/portal) 2. Click on **Create a new app** 3. Specify a name for the integration, example: **Inkeep Widget** 4. Select your community from the drop-down menu below 5. Click on **Create app** Result: {" "} ## Customizing App Next, you need to go to the app you created and customize the widget. ### Initialize the widget Click on the **Custom code** section and add the following script to the `Custom code for ` area: ```html ``` Click on the **Test and publish** section and click **Publish**. ## Install App Open community settings, then open the **Apps** section. Select the app you created. Go back to the main community page and reload the page. # Add an AI chatbot to your Discord server ## Overview You can add Inkeep as a [Discord](https://discord.com/) app to your Discord community or team's internal support, solutions engineering, or other channels. ## Get Server ID To configure the bot, you'll need your Discord Server ID: 1. Open Discord in your [browser](https://discord.com/app). 2. Select the target server in the left navigation bar 3. Copy the `{SERVER_ID}` from the browser's navigation bar. The URL will be in the format of `https://discord.com/channels/{SERVER_ID}/{CHANNEL_ID}` 4. Alternatively, you can find the ID in server settings. ## Create Integration To use the Inkeep Discord bot, we first need to register it as an integration. 1. Open the [Inkeep Dashboard](https://portal.inkeep.com) 2. Navigate to the **Integrations** tab within the desired project 3. Click on **Create Integration** 4. From the dropdown menu, choose **Discord** 5. Fill in the required fields **Name** and **Server ID**. 6. (Optional) If you'd like the bot to *only* reply to tags in a specific channel, specify the channel ID in **Only reply in selected Channel IDs** under **Advanced Settings**. By default, the bot is able to be tagged in any public channel or private channels it is added to. 7. Click on **Create** ## Add to Server To install the Inkeep Discord bot in your server: 1. Click [here](https://install.inkeep.com/ask-discord) 2. Select the desired server in the **ADD TO SERVER** dropdown 3. Review the permissions and click **Continue** The Discord bot can be added to any type of channel, including forums or normal chat-style channels. For private channels, ensure that the bot is added as a member of the channel. To start using the bot, tag **@Ask Inkeep** in a message with your question. You or your users can do this in any channel. ## Create a dedicated channel (optional) We recommend having a dedicated channel for team or community members to ask questions to the bot. 1. Create an `✨ask-ai` or similar channel. 2. Ask an example question in the channel to demonstrate how to use the bot. Type **@Ask Inkeep** before your question and hit enter. 3. The Inkeep bot will automatically create a new thread (if [enabled](https://support.discord.com/hc/en-us/articles/4403205878423-Threads-FAQ#h_01FBM7FY3D1J3WC3TP71EC7JRF)) and reply to the user. 4. For follow-up questions, the user **must** tag the bot again. Since Discord does not have the ability to pin a channel, consider: * Making an announcement in your `#general` or other relevant channels linking users to it * Adding guidance on how to use the bot in a pinned message on your main channel or incorporate as part of your onboarding experience. ## Customize the Display Name Instead of using **@Ask Inkeep**, you can customize the bot name from Discord. 1. Have at least one message that tags **@Ask Inkeep** 2. In that message, *right* click on **@Ask Inkeep** 3. Select **Change Nickname** 4. Enter your desired name for the bot, e.g. `Ask CompanyAI` The bot can now be tagged via **@Ask CompanyAI** and all existing instances of **@Ask Inkeep** will be updated. Unfortunately, Discord does not currently support custom avatars or icons for published bots. ## Tag a team member Sometimes, you want users to be able to escalate to a human for help if the bot is not able to help. To make this flow seamless, you can configure the Discord integration to show **Mark as resolved ✅** and **Ask for help 👋** buttons instead of the default 👍 👎. When a user clicks on **Ask for help 👋**, the bot can tag users, roles, or other bots. To set up: 1. Open the [Inkeep Dashboard](https://portal.inkeep.com) 2. Navigate to the **Integrations** tab within the desired project 3. Select the Discord Integration 4. Expand **Advanced Settings** 5. Under **When a user leaves negative feedback...**, click the dropdown menu 6. Select **Tag a team member** 7. Specify the **User IDs**, **Role IDs**, or **Bot IDs** you'd like to tag You can get the ID of a Bot or User by right-clicking on a profile on Discord and clicking on **Copy User ID** or **Copy group ID**. For a Role, you'll need to: 1. Navigate to the server 2. On the top left of the Discord app, click on your server name 3. Click on **Server Settings** 4. Navigate to **Roles** from the left side pane 5. Click on **...** next to the desired role and select **Copy Role ID** ## Auto-reply mode Instead of requiring users to tag the bot with `@Ask Inkeep`, you can configure the Discord bot to automatically reply to all new threads in specific channels. This can be useful for dedicated support channels or community Q\&A forums. To set up auto-reply mode: 1. Open the [Inkeep Dashboard](https://portal.inkeep.com) 2. Navigate to the **Integrations** tab within the desired project 3. Select the Discord Integration 4. Expand **Advanced Settings** 5. In the **Auto-reply Channel IDs** field, add the channel IDs where you want the bot to auto-reply to new threads 6. (Optional) Customize the bot's behavior with these settings: * **Reply message**: Enter a custom initial message for the bot to use when it's tagged or auto-replies * **Is AI Draft Mode enabled?**: Adjusts the bot's tone for generating draft responses for team review * **Is human reviewing conversations?**: Informs users that team members are monitoring conversations 7. Click **Save** to apply the changes We generally don't recommend enabling auto-reply for all channels, as it's beneficial for users to get accustomed to tagging the bot, especially for follow-up questions. ### Limiting bot responses to specific channels If you want to restrict the bot to only respond in certain channels: 1. In the **Advanced Settings**, find the **Only reply in selected Channel IDs** field 2. Add the channel IDs where you want the bot to be active 3. The bot will now only respond when tagged in these specified channels This can be useful for managing the bot's presence in large servers or for creating dedicated AI assistance channels. We recommend creating a custom bot, e.g. `@Triaging Bot`, that contains the logic you'd like to use for triggering custom down-stream workflows. Need to make Discord SEO-friendly? Try [Answer Overflow](https://www.answeroverflow.com?utm=inkeep) ↗️ (partner). # Add AI Chat to your Discourse community ## Overview You can add AI chat as a [Discourse](https://www.discourse.org/) widget to your Discourse community. ## Create Theme-Component To use the AI chat, first we need to create a theme-component on the Discourse platform. 1. Click the **Admin** section in the sidebar menu 2. Open the **Customize** tab 3. Open the **Themes** subsection 4. Now, select the **Components** tab 5. Click on **Install** 6. Open the **Create new** menu and specify a name for the component 7. Click on **Create** Example: ## Customizing Theme-Component Next, in the created theme component menu, click **Edit CSS/HTML** to customize the widget. ### Initialize the widget Click on the **Head** section and paste the following scripts: ```html Base Theme ``` ```html Material Design Theme ``` Optional: click **preview** to test out the widget. Click on the **Save**. ## Apply to themes In the **Include component on these themes** setting, specify the themes in which the widget will be used. Alternatively, you can create and specify your own theme. ## Allow list from Content Security Policy (CSP) If you are using a Discourse version earlier than `3.3.0.beta1` you may need to allow-list the Inkeep script by modifying the Content Security Policy (CSP). In version `3.3.0.beta1` or later external scripts should work without additional configuration, see [here](https://meta.discourse.org/t/mitigate-xss-attacks-with-content-security-policy/104243#csp-and-third-party-integrations-8) for more details: 1. Click the **Admin** section in the sidebar menu 2. Open the **Settings** tab 3. Open the **Security** section 4. Add `https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js` to the **content security policy script src** setting 5. Enable **content security policy strict dynamic** setting Example: Go back to the main community page and reload the page. # Add AI Chat to your Document360 docs ## Initialize the widget To integrate the Inkeep chat button into your Document360 documentation, follow these steps: 1. Navigate to **Custom CSS / Javascript** settings. 2. Click on the **Javascript** tab. 3. Add the following script to load the chat button: ```js document.addEventListener("DOMContentLoaded", () => { // Load the Inkeep script const inkeepScript = document.createElement("script"); inkeepScript.src = "https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js"; inkeepScript.type = "module"; inkeepScript.defer = true; document.head.appendChild(inkeepScript); // Configure and initialize the widget const addInkeepWidget = () => { const inkeepWidget = Inkeep().embed({ componentType: "ChatButton", colorModeSync: { observedElement: document.documentElement, isDarkModeCallback: (el) => { const currentTheme = el.getAttribute("data-color-mode"); return currentTheme === "dark"; }, colorModeAttribute: "data-color-mode", }, properties: { chatButtonType: "PILL", baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "https://mydomain.com/mylogo", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); }; inkeepScript.addEventListener("load", () => { addInkeepWidget(); }); }); ``` # Add AI Search & Chat Button to your Document360 docs ## Initialize the widget To integrate the Inkeep chat button and search bar into your Document360 documentation, follow these steps: 1. On the **Home page** remove the default search bar from the **Hero** section 2. Go to the page where you want to display the widget and create new **Custom code section** with the following html: ```html
``` 3. Navigate to **Custom CSS / Javascript** settings. 4. Click on the **Javascript** tab. 5. Add the following script to load the inkeep chat button and search bar: ```js document.addEventListener("DOMContentLoaded", () => { // Load the Inkeep script const inkeepScript = document.createElement("script"); inkeepScript.src = "https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js"; inkeepScript.type = "module"; inkeepScript.defer = true; document.head.appendChild(inkeepScript); // Function for customizing the widget configuration const inkeepConfig = (componentType, targetElementId) => ({ componentType, targetElement: targetElementId, properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { // stylesheetUrls: ['/path/to/stylesheets'], // optional // ...optional settings }, }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); const addSearchbarWidget = () => { Inkeep().embed(inkeepConfig("SearchBar", "#inkeepSearchBar")); const observer = new MutationObserver((mutationsList, observer) => { const inkeepPortals = document.getElementsByTagName("inkeep-portal"); Array.from(inkeepPortals)?.forEach((inkeepPortal) => { inkeepPortal.remove(); }); inkeepConfig("SearchBar", "#inkeepSearchBar").targetElement = search; Inkeep().embed(inkeepConfig("SearchBar", "#inkeepSearchBar")); }); observer.observe(document.head, { childList: true }); }; inkeepScript.addEventListener("load", () => { addSearchbarWidget(); Inkeep().embed(inkeepConfig("ChatButton")); }); }); ``` # Add AI Search to your Document360 docs ## Initialize the widget To integrate the Inkeep search bar into your Document360 documentation, follow these steps: 1. On the **Home page** remove the default search bar from the **Hero** section 2. Go to the page where you want to display the widget and create a new **Custom code section** with the following html code: ```html
``` 3. Navigate to **Custom CSS / Javascript** settings. 4. Click on the **Javascript** tab. 5. Add the following script to disable default search bar and load the inkeep one: ```js document.addEventListener("DOMContentLoaded", () => { // Load the Inkeep script const inkeepScript = document.createElement("script"); inkeepScript.src = "https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js"; inkeepScript.type = "module"; inkeepScript.defer = true; document.head.appendChild(inkeepScript); // Function for customizing the widget configuration const inkeepConfig = (targetElementId) => ({ componentType: "SearchBar", targetElement: targetElementId, properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { // stylesheetUrls: ['/path/to/stylesheets'], // optional // ...optional settings }, }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); const addInkeepWidget = () => { Inkeep().embed(inkeepConfig("#inkeepSearchBar")); const observer = new MutationObserver((mutationsList, observer) => { const inkeepPortals = document.getElementsByTagName("inkeep-portal"); Array.from(inkeepPortals)?.forEach((inkeepPortal) => { inkeepPortal.remove(); }); inkeepConfig("#inkeepSearchBar").targetElement = search; Inkeep().embed(inkeepConfig("#inkeepSearchBar")); }); observer.observe(document.head, { childList: true }); }; inkeepScript.addEventListener("load", addInkeepWidget); }); ``` # Add AI Chat to your Docusaurus docs ## What is Docusaurus [Docusaurus](https://docusaurus.io/) is an open-source documentation platform powered by MDX and React. ## Define the widget Аdd the chat button as a theme in your `docusaurus.config.js` file: ```js docusaurus.config.js themes: ["@inkeep/docusaurus/chatButton"], ``` ### Сonfiguration settings Next, configure the widget in the `themeConfig` property: ```js docusaurus.config.js //.. themeConfig: { inkeepConfig: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // required -- your brand color, the widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { // stylesheetUrls: ['/path/to/stylesheets'], // optional syntaxHighlighter: { lightTheme: lightCodeTheme, // optional -- pass in the Prism theme you're using darkTheme: darkCodeTheme, // optional -- pass in the Prism theme you're using }, } }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // optional -- use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }, ``` # Add AI Search & Chat to your Docusaurus docs ## What is Docusaurus [Docusaurus](https://docusaurus.io/) is an open-source documentation platform powered by MDX and React. ## Configure the widget Аdd the Inkeep widgets as themes in the `docusaurus.config.js` file: ```js docusaurus.config.js themes: ["@inkeep/docusaurus/chatButton", "@inkeep/docusaurus/searchBar"], ``` ### Сonfiguration settings Next, configure the widgets in the `themeConfig` property: ```js docusaurus.config.js //.. themeConfig: { inkeepConfig: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // required -- your brand color, the widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { // stylesheetUrls: ['/path/to/stylesheets'], // optional syntaxHighlighter: { lightTheme: lightCodeTheme, // optional -- pass in the Prism theme you're using darkTheme: darkCodeTheme, // optional -- pass in the Prism theme you're using }, } }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }, ``` # Add AI Search to your Docusaurus docs ## What is Docusaurus [Docusaurus](https://docusaurus.io/) is an open-source documentation platform powered by MDX and React. ## Define the widget Аdd the search bar as a theme in your `docusaurus.config.js` file: ```js docusaurus.config.js themes: ["@inkeep/docusaurus/searchBar"], ``` If you are already using `Algolia DocSearch` provided by Docusaurus by default, it will be replaced by our widget. ### Сonfiguration settings Next, add the below to the `themeConfig` property: ```js docusaurus.config.js //.. themeConfig: { inkeepConfig: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // required -- your brand color, the widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { // stylesheetUrls: ['/path/to/stylesheets'], // optional syntaxHighlighter: { lightTheme: lightCodeTheme, // optional -- pass in the Prism theme you're using darkTheme: darkCodeTheme, // optional -- pass in the Prism theme you're using }, } }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // optional -- use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }, ``` If you have already created a custom `SearchBar` component (for example via `swizzle eject`) this will need to be removed in order to use our Search Bar. # Add AI Chat to your Framer website ## Initialize the widget Next, paste the script below into the **End of `` tag** section: ```html ``` ## Save custom code Click on the **Save** button. Navigate to your web application and reload the page, to make sure the widget is installed correctly. # Add AI Search to your Framer website ## Creating the container Add a widget container to the page using the built-in **Framer** functionality. 1. Select an area to add a container 2. Select the **Embed** item in the **Utility** section 3. Add an html element to the page 4. Specify the ID of the container to be `inkeepSearchBar` ## Initialize the widget Next, paste the script below into the **End of `` tag** section: ```html ``` ## Save custom code Click on the **Save** button. Navigate to your web application and reload the page, to make sure the widget is installed correctly. # Add Inkeep's UI components to your Fumadocs ## Initialize the widget Create an `inkeep-script.tsx` client component in your Fumadocs project. ```jsx inkeep-script.tsx "use client"; declare global { interface Window { Inkeep: any; } } import Script from "next/script"; export function InkeepScript() { return ( ``` 5. Update this script with your API key and desired settings. 6. For Triggering, choose **All Pages**. 7. Click save to have the Chat Button appear on your GTM synced website ## Other Snippets For the other integrations please refer to the JS Snippet examples in the UI Components Page section * [Search Bar](/ui-components/js-snippet/search-bar) * [Embedded Chat](/ui-components/js-snippet/embedded-chat) * [Custom Trigger](/ui-components/js-snippet/custom-trigger) # Add AI Chat to your Hugo docs ## Overview In this integration example, we will be integrating Inkeep widget into [Ananke](https://themes.gohugo.io/themes/gohugo-theme-ananke/) theme, which is the default theme used as an example with Hugo. ## What is Hugo [Hugo](https://gohugo.io/) is a popular open-source static site generator often used for documentation. ## Create the baseof.html file Create an `baseof.html` file at the path `layouts/_default` ```bash touch layouts/_default/baseof.html ``` Copy the contents of the `baseof.html` file: ```bash cp themes/ananke/layouts/_default/baseof.html layouts/_default/baseof.html ``` Depending on your Hugo theme, this step may differ. The goal is to be able to insert custom scripts into your sites `` tag. ### Load the script files In the `baseof.html` file, locate the **head** tag and paste the scripts below at the end: ```html baseof.html ``` ## Create the addInkeep.js script Create an `addInkeep.js` file in your `static/js` folder. ```bash touch static/js/addInkeep.js ``` Now, configure the chat button component. ```js addInkeep.js const inkeepWidget = Inkeep().embed({ componentType: "ChatButton", properties: { chatButtonType: "PILL", baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); ``` # Add AI Chat to your Docsy docs Now, configure the chat button component. ```js addInkeep.js // Embed the widget using the `Inkeep.embed()` function. const inkeepWidget = Inkeep().embed({ componentType: "ChatButton", properties: { chatButtonType: "PILL", baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); ``` # Add AI Search to your Docsy docs Disable the default search in the `config.toml (or hugo.toml)` file, by setting the `offlineSearch` parameter to **false**. Now, create a container and configure the search bar component. ```js addInkeep.js // Create an HTML element that the Inkeep widget will be inserted into. const nav = document.querySelector("nav"); const sidebar = document.getElementById("td-sidebar-menu"); const inkeepNavDiv = document.createElement("div"); inkeepNavDiv.id = "navSearchBar"; nav.appendChild(inkeepNavDiv); const inkeepSidebarDiv = document.createElement("div"); inkeepSidebarDiv.id = "sideSearchBar"; sidebar && sidebar.prepend(inkeepSidebarDiv); // Function for initializating the widget. const addInkeepWidget = ({ targetElement, stylesheetUrls, isShortcutKeyEnabled, }) => { // Embed the widget using the `Inkeep.embed()` function. const inkeepWidget = Inkeep().embed({ componentType: "SearchBar", targetElement, properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { stylesheetUrls, // ...optional settings }, }, modalSettings: { // optional settings isShortcutKeyEnabled, }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); }; sidebar && addInkeepWidget({ targetElement: document.getElementById("sideSearchBar"), stylesheetUrls: ['/path/to/stylesheets'], // optional isShortcutKeyEnabled: false, }); addInkeepWidget({ targetElement: document.getElementById("navSearchBar"), stylesheetUrls: ['/path/to/stylesheets'], // optional isShortcutKeyEnabled: true, }); ``` # Add Inkeep's UI components to your Mintlify docs ## Overview [Mintlify](https://mintlify.com) is a managed documentation platform with a fresh, modern look. You can leverage Inkeep to extend the default functionality provided by the Mintlify platform with: * [Additional sources](/sources/onboard-new-sources), like GitHub repos and community forums. * Extensible search bar, chat button, and embedded chat UI components you can add to your help desk, app, docs, or marketing site. * Slack and Discord bots for your community or internal team channels * AI tools for support teams To add Inkeep's search bar or "Ask AI" chat button to your Mintlify docs, you can add a script file to the root of your docs repo. Check out the search bar and chat button on this page for an example. ## Add a script to your repo Create an `inkeep.js` file at the root of your documentation GitHub repo like the example below. Customize `inkeepSettings` with your API key and other [customizations](/ui-components/common-settings). By default, the below script adds both Inkeep's search bar and AI chat button. ```js inkeep.js // customize const inkeepSettings = { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // required -- your brand color, the color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, aiChatSettings: { // ...optional settings botAvatarSrcUrl: "https://mydomain.com/mylogo.svg", quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, modalSettings: { isShortcutKeyEnabled: false, // disable default cmd+k behavior // ...optional settings }, }; // The Mintlify search triggers, which we'll reuse to trigger the Inkeep modal const searchButtonContainerIds = [ "search-bar-entry", "search-bar-entry-mobile", ]; // Clone and replace, needed to remove existing event listeners const clonedSearchButtonContainers = searchButtonContainerIds.map((id) => { const originalElement = document.getElementById(id); const clonedElement = originalElement.cloneNode(true); originalElement.parentNode.replaceChild(clonedElement, originalElement); return clonedElement; }); // Load the Inkeep component library const inkeepScript = document.createElement("script"); inkeepScript.type = "module"; inkeepScript.src = "https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js"; document.body.appendChild(inkeepScript); // Once the Inkeep library is loaded, instantiate the UI components inkeepScript.addEventListener("load", function () { // Customization settings // for syncing with dark mode const colorModeSettings = { observedElement: document.documentElement, isDarkModeCallback: (el) => { return el.classList.contains("dark"); }, colorModeAttribute: "class", }; // Instantiate the 'Ask AI' pill chat button. Optional. Inkeep().embed({ componentType: "ChatButton", colorModeSync: colorModeSettings, properties: inkeepSettings, }); // Instantiate the search bar modal const inkeepSearchModal = Inkeep({ ...inkeepSettings.baseSettings, }).embed({ componentType: "CustomTrigger", colorModeSync: colorModeSettings, properties: { ...inkeepSettings, isOpen: false, onClose: () => { inkeepSearchModal.render({ isOpen: false, }); }, }, }); // When the Mintlify search bar elements are clicked, open the Inkeep search modal clonedSearchButtonContainers.forEach((trigger) => { trigger.addEventListener("click", function () { inkeepSearchModal.render({ isOpen: true, }); }); }); // Open the Inkeep Modal with cmd+k window.addEventListener( "keydown", (event) => { if ( (event.metaKey || event.ctrlKey) && (event.key === "k" || event.key === "K") ) { event.stopPropagation(); inkeepSearchModal.render({ isOpen: true }); return false; } }, true ); }); ``` If custom JavaScript is not part of your plan, [let us know](mailto:support@inkeep.com), and we'll work with the Mintlify team to enable it for you under your current plan. # Add AI Chat to your MkDocs docs ## Initialize the widget In docs folder create a custom js file, for example `inkeep-button.js`. Add path to this file to the `mkdocs.yml`: ```yml mkdocs.yml extra_javascript: - inkeep-button.js ``` Then add the button: ```js inkeep-button.js document.addEventListener("DOMContentLoaded", () => { // Load the Inkeep script const inkeepScript = document.createElement("script"); inkeepScript.src = "https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js"; inkeepScript.type = "module"; inkeepScript.defer = true; document.head.appendChild(inkeepScript); // Configure and initialize the widget const addInkeepWidget = () => { const inkeepWidget = Inkeep().embed({ componentType: "ChatButton", colorModeSync: { observedElement: document.documentElement, isDarkModeCallback: (el) => { const currentTheme = el.getAttribute("data-color-mode"); return currentTheme === "dark"; }, colorModeAttribute: "data-color-mode", }, properties: { chatButtonType: "PILL", baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "https://mydomain.com/mylogo", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); }; inkeepScript.addEventListener("load", () => { addInkeepWidget(); // initialize the widget }); }); ``` # Add AI Search & Chat Button to your MkDocs docs ## Initialize the widget In docs folder create a custom js file, for example `button-and-search-bar.js`. Add path to this file to the `mkdocs.yml` and disable default search bar with `plugins: []`: ```yml mkdocs.yml extra_javascript: - button-and-search-bar.js plugins: [] ``` Then add the button and search bar: ```js button-and-search-bar.js document.addEventListener("DOMContentLoaded", () => { // Load the Inkeep script const inkeepScript = document.createElement("script"); inkeepScript.src = "https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js"; inkeepScript.type = "module"; inkeepScript.defer = true; document.head.appendChild(inkeepScript); // Create a new div for the Inkeep search bar const inkeepDiv = document.createElement("div"); inkeepDiv.id = "inkeepSearchBar"; // Get the header element where you want to place the Inkeep search bar const headerElement = document.querySelector("#navbar-collapse"); if (headerElement) { headerElement.appendChild(inkeepDiv); } // configure and initialize the widget const addInkeepWidget = (componentType, targetElementId) => { const inkeepWidget = Inkeep().embed({ componentType, ...(componentType !== "ChatButton" ? { targetElement: targetElementId } : {}), colorModeSync: { observedElement: document.documentElement, isDarkModeCallback: (el) => { const currentTheme = el.getAttribute("data-color-mode"); return currentTheme === "dracula" || currentTheme === "dark"; }, colorModeAttribute: "data-color-mode-scheme", }, properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings, theme: { // stylesheetUrls: ["/path/to/stylesheets"], // optional // ...optionalSettings, }, }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "https://mydomain.com/mylogo", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); }; inkeepScript.addEventListener("load", () => { const widgetContainer = document.getElementById("inkeepSearchBar"); addInkeepWidget("ChatButton"); widgetContainer && addInkeepWidget("SearchBar", "#inkeepSearchBar"); }); }); ``` # Add AI Search to your MkDocs docs ## Initialize the widget In docs folder create a custom js file, for example `search-bar.js`. Add path to this file to the `mkdocs.yml` and disable default search bar with `plugins: []`: ```yml mkdocs.yml extra_javascript: - search-bar.js plugins: [] ``` Then add the search bar: ```js search-bar.js document.addEventListener("DOMContentLoaded", () => { // Create a new div for the Inkeep search bar const inkeepDiv = document.createElement("div"); inkeepDiv.id = "inkeepSearchBar"; // Get the header element where you want to place the Inkeep search bar const headerElement = document.querySelector(".navbar-nav"); if (headerElement) { headerElement.parentNode.insertBefore(inkeepDiv, headerElement.nextSibling); } // Load the Inkeep script const inkeepScript = document.createElement("script"); inkeepScript.src = "https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js"; inkeepScript.type = "module"; inkeepScript.defer = true; document.head.appendChild(inkeepScript); // Initialize the Inkeep widget after the script loads inkeepScript.addEventListener("load", () => { Inkeep().embed({ componentType: "SearchBar", targetElement: "#inkeepSearchBar", colorModeSync: { observedElement: document.documentElement, isDarkModeCallback: (el) => { const currentTheme = el.getAttribute("data-md-color-scheme"); return currentTheme === "dracula" || currentTheme === "dark"; }, colorModeAttribute: "data-md-color-scheme", }, properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", organizationDisplayName: "Inkeep", }, }, }); }); }); ``` # Add AI Chat to your Next.js app ### Define the component Next, create an `InkeepChatButton.tsx` file for our [`Chat Button`](https://docs.inkeep.com/ui-components/react/chat-button) component. # Add a Custom Trigger to your Next.js app Trigger the Inkeep modal with a custom button in your Next.js app. ### Define the component Create an `InkeepCustomTrigger.tsx` file for our [`Custom Trigger`](https://docs.inkeep.com/ui-components/react/custom-trigger) component. `
); } export default InkeepCustomTrigger; ``` ```tsx .tsx import dynamic from 'next/dynamic'; import { useCallback, useState } from 'react'; import { InkeepCustomTriggerProps } from '@inkeep/uikit'; import useInkeepSettings from '@/utils/useInkeepSettings'; const CustomTrigger = dynamic( () => import('@inkeep/uikit').then((mod) => mod.InkeepCustomTrigger), { ssr: false, }, ); function InkeepCustomTrigger() { const [isOpen, setIsOpen] = useState(false); const { baseSettings, aiChatSettings, searchSettings, modalSettings } = useInkeepSettings(); const handleClose = useCallback(() => { console.log("Modal closed"); setIsOpen(false); }, []); const customTriggerProps: InkeepCustomTriggerProps = { isOpen, onClose: handleClose, baseSettings, aiChatSettings, searchSettings, modalSettings, }; return (
); } export default InkeepCustomTrigger; ``` # Add an Embedded Chat to your Next.js app Add AI chat to your Next.js application. ### Define the component Next, create an `InkeepEmbeddedChat.tsx` component file for our [`Embedded Chat`](https://docs.inkeep.com/ui-components/react/embedded-chat) component. ```jsx .jsx import dynamic from 'next/dynamic'; import useInkeepSettings from '@/utils/useInkeepSettings'; const EmbeddedChat = dynamic( () => import('@inkeep/uikit').then((mod) => mod.InkeepEmbeddedChat), { ssr: false, // loading: () =>
loading...
, // optional: loading animation component }, ); function InkeepEmbeddedChat() { const { baseSettings, aiChatSettings, } = useInkeepSettings(); const embeddedChatProps = { baseSettings, aiChatSettings, }; return ; } export default InkeepEmbeddedChat; ``` ```tsx .tsx import dynamic from 'next/dynamic'; import { InkeepEmbeddedChatProps } from '@inkeep/uikit'; import useInkeepSettings from '@/utils/useInkeepSettings'; const EmbeddedChat = dynamic( () => import('@inkeep/uikit').then((mod) => mod.InkeepEmbeddedChat), { ssr: false, // loading: () =>
loading...
, // optional: loading animation component }, ); function InkeepEmbeddedChat() { const { baseSettings, aiChatSettings, } = useInkeepSettings(); const embeddedChatProps: InkeepEmbeddedChatProps = { baseSettings, aiChatSettings, }; return ; } export default InkeepEmbeddedChat; ```
# Next 15 Using the uikit with Next 15 and React 19. The `@inkeep/uikit` package supports React 18, in order to use the Inkeep components with Next 15 and React 19 you can install the `@inkeep/uikit-js` package. #### Example To ensure the uikit is only loaded client side, you will need to import the library asynchronously within a useEffect hook. This example uses the Chat Button component but the same logic can be applied to any of the other components. See [here](/ui-components/js-snippet) for additional details on using the `uikit-js` package. ```tsx 'use client'; import { useEffect, useRef } from "react"; const baseSettings = { integrationId: process.env.NEXT_PUBLIC_INKEEP_API_KEY, apiKey: process.env.NEXT_PUBLIC_INKEEP_INTEGRATION_ID, organizationId: process.env.NEXT_PUBLIC_INKEEP_ORGANIZATION_ID, primaryBrandColor: "#000000", }; export const ChatButton = () => { const chatButtonRef = useRef(null); useEffect(() => { const loadInkeepJS = async () => { const inkeepJS = await import("@inkeep/uikit-js"); const inkeep = inkeepJS.Inkeep(baseSettings); chatButtonRef.current = inkeep.embed({ componentType: "ChatButton", properties: { chatButtonType: "PILL", baseSettings, aiChatSettings: { quickQuestions: ["How to get started?"], }, }, }); }; loadInkeepJS(); }, []); return null; }; ``` # Add AI Search to your Next.js app ### Define the component Next, create an `InkeepSearchBar.tsx` component file for our [`Search Bar`](https://docs.inkeep.com/ui-components/react/search-bar) component. # Add AI Chat to your Nextra docs ### Define the component Next, create an `InkeepChatButton.tsx` file for the [`Chat Button`](https://docs.inkeep.com/ui-components/react/chat-button) component. ### Modify footer To add a widget to the page, paste the code shown below into the `theme.config.tsx` file: ```tsx theme.config.tsx //... footer: { component: () => } ``` # Add AI Search & Chat Button to your Nextra docs ### Define the components Next, create component files `InkeepChatButton.tsx` and `InkeepSearchBar.tsx` #### ChatButton #### SearchBar ### Modify theme config To add widgets to the page, paste the code shown below into the `theme.config.tsx` file: ```tsx theme.config.tsx //... footer: { component: () => }, search: { component: () => } ``` # Add a Custom Trigger to your Nextra docs ### Define the component Create an `InkeepCustomTrigger.tsx` file for the [`Custom Trigger`](https://docs.inkeep.com/ui-components/react/custom-trigger) component. ```jsx .jsx import { useCallback, useEffect, useState } from "react"; import useInkeepSettings from "@/utils/useInkeepSettings"; export default function InkeepCustomTrigger() { const [isOpen, setIsOpen] = useState(false); const [CustomTirgger, setCustomTrigger] = useState(); const handleClose = useCallback(() => { console.log("Modal closed"); setIsOpen(false); }, []); const { baseSettings, aiChatSettings, searchSettings, modalSettings } = useInkeepSettings(); // load the library asynchronously useEffect(() => { const loadCustomTrigger = async () => { try { const { InkeepCustomTrigger } = await import("@inkeep/uikit"); setCustomTrigger(() => InkeepCustomTrigger); } catch (error) { console.error("Failed to load CustomTirgger:", error); } }; loadCustomTrigger(); }, []); const customTriggerProps = { isOpen, onClose: handleClose, baseSettings, aiChatSettings, searchSettings, modalSettings, }; return (
{CustomTirgger && }
) } ``` ```tsx .tsx import { useCallback, useEffect, useState } from "react"; import useInkeepSettings from "@/utils/useInkeepSettings"; import type { InkeepCustomTriggerProps } from "@inkeep/uikit"; export default function InkeepCustomTrigger() { const [isOpen, setIsOpen] = useState(false); const [CustomTirgger, setCustomTrigger] = useState<(e: InkeepCustomTriggerProps) => JSX.Element>(); const handleClose = useCallback(() => { console.log("Modal closed"); setIsOpen(false); }, []); const { baseSettings, aiChatSettings, searchSettings, modalSettings } = useInkeepSettings(); // load the library asynchronously useEffect(() => { const loadCustomTrigger = async () => { try { const { InkeepCustomTrigger } = await import("@inkeep/uikit"); setCustomTrigger(() => InkeepCustomTrigger); } catch (error) { console.error("Failed to load CustomTirgger:", error); } }; loadCustomTrigger(); }, []); const customTriggerProps: InkeepCustomTriggerProps = { isOpen, onClose: handleClose, baseSettings, aiChatSettings, searchSettings, modalSettings, }; return (
{CustomTirgger && }
) } ```
### Use the component Add a widget component to the page: ```jsx import InkeepCustomTrigger from "@/components/InkeepCustomTrigger"; export default function MyApp() { return ( <> ) } ``` # Add an Embedded AI Chat to your Nextra docs ### Define the component Create an `InkeepEmbeddedChat.tsx` file for the [`Embedded Chat`](https://docs.inkeep.com/ui-components/react/embedded-chat) component. ```jsx .jsx import { useEffect, useState } from "react"; import useInkeepSettings from "@/utils/useInkeepSettings"; export default function InkeepEmbeddedChat() { const [EmbeddedChat, setEmbeddedChat] = useState(null); const { baseSettings, aiChatSettings } = useInkeepSettings(); // load the library asynchronously useEffect(() => { const loadEmbeddedChat = async () => { try { const { InkeepEmbeddedChat } = await import("@inkeep/uikit"); setEmbeddedChat(() => InkeepEmbeddedChat); } catch (error) { console.error("Failed to load EmbeddedChat:", error); } }; loadEmbeddedChat(); }, []); const embeddedChatProps = { baseSettings, aiChatSettings, }; return EmbeddedChat && ; } ``` ```tsx .tsx import { ReactNode, useEffect, useState } from "react"; import useInkeepSettings from "@/utils/useInkeepSettings"; import type { InkeepEmbeddedChatProps } from "@inkeep/uikit"; export default function InkeepEmbeddedChat() { const [EmbeddedChat, setEmbeddedChat] = useState<(e: InkeepEmbeddedChatProps) => JSX.Element | ReactNode>(); const { baseSettings, aiChatSettings } = useInkeepSettings(); // load the library asynchronously useEffect(() => { const loadEmbeddedChat = async () => { try { const { InkeepEmbeddedChat } = await import("@inkeep/uikit"); setEmbeddedChat(() => InkeepEmbeddedChat); } catch (error) { console.error("Failed to load EmbeddedChat:", error); } }; loadEmbeddedChat(); }, []); const embeddedChatProps: InkeepEmbeddedChatProps = { baseSettings, aiChatSettings, }; return EmbeddedChat && ; } ``` ### Use the component Now to add the `InkeepEmbeddedChat.tsx` component follow these steps: 1. Create a new page - **Ask AI ✨** ```bash touch pages/ask-ai.mdx ``` 2. Initialize the page in the `_meta.json` file: ```json _meta.json { // ... "ask-ai": "Ask AI ✨" // ... } ``` 3. Add a widget to the page: ```jsx ask-ai.mdx # Ask AI ✨ import InkeepEmbeddedChat from "@/components/InkeepEmbeddedChat"; ``` # Add AI Search to your Nextra docs ### Define the component Next, create an `InkeepSearchBar.tsx` file for the [`Search Bar`](https://docs.inkeep.com/ui-components/react/search-bar) component. ### Replace default search bar To add a widget to the page, paste the code shown below into the `theme.config.tsx` file: ```tsx theme.config.tsx //... search: { component: () => } ``` Search bar will be replaced by this widget by default # Add AI Chat to your ReadMe docs ## Initialize the widget Next, in the **FOOTER HTML** section add the below code to add the chat button widget: ```html ``` Press **Save** to apply your changes. # Add AI Search & Chat Button to your ReadMe docs ## Initialize the widgets Next, in the **FOOTER HTML** section add the below code to hide the default search bar: ```html ``` Then add the chat button and search bar: ```html ``` Press **Save** to apply your changes. # Add AI Search to your ReadMe docs ## Initialize the widget Next, in the **FOOTER HTML** section add the below code to hide the default search bar: ```html ``` Then add the search bar: ```html ``` Press **Save** to apply your changes. # Add AI Chat to your Redocly API docs Now, configure the chat button component. ```js addInkeep.js // Embed the widget using the `Inkeep.embed()` function. const inkeepWidget = Inkeep().embed({ componentType: "ChatButton", properties: { chatButtonType: "PILL", baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); ``` # Add AI Search to Redocly API docs Now, create a container and configure the search bar component. ```js addInkeep.js // Create an HTML element that the Inkeep widget will be inserted into. const searchBarParentNode = document.querySelector(".scrollbar-container"); const inkeepDiv = document.createElement("div"); inkeepDiv.id = "inkeep"; searchBarParentNode.prepend(inkeepDiv); let inkeepWidget = null; const targetElement = document.getElementById("inkeep"); const config = { componentType: "SearchBar", targetElement, properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { // stylesheetUrls: ['/path/to/stylesheets'], // optional // ...optional settings }, }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }; // Wait for container to render if (!inkeepWidget && targetElement) { // Embed the widget using the `Inkeep.embed()` function. inkeepWidget = Inkeep().embed(config); } ``` # Add AI Chat to your Redocly developer portal Now, configure the chat button component. ```js addInkeep.js // Function for adding a script to the page const addScript = (src, type = "module", defer = true) => { const script = document.createElement("script"); script.src = src; script.type = type; script.defer = defer; document.body.appendChild(script); return script; }; // Function for customizing the widget configuration const inkeepConfig = () => { return { componentType: "ChatButton", properties: { chatButtonType: "PILL", // <-- the "Pill" variation baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }; }; // Function for initializating the widget const addInkeepWidget = () => { const inkeepWidgetScript = document.createElement("script"); inkeepWidgetScript.defer = true; // Embed the widget using the `Inkeep.embed()` function. inkeepWidgetScript.innerHTML = ` const config = (${inkeepConfig.toString()})(); Inkeep().embed(config); `; document.body.appendChild(inkeepWidgetScript); }; // Adding the script and initializing the widget const embedScript = addScript( "https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js" ); embedScript.addEventListener("load", () => { addInkeepWidget(); }); ``` # Add AI Search to your Redocly developer portal Now, create a container and configure the search bar component. ```js addInkeep.js // Function for adding a script to the page const addScript = (src, type = "module", defer = true) => { const script = document.createElement("script"); script.src = src; script.type = type; script.defer = defer; document.body.appendChild(script); return script; }; // Function for customizing the widget configuration const inkeepConfig = (targetElement) => ({ componentType: "SearchBar", targetElement, properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { // stylesheetUrls: ['/path/to/stylesheets'], // optional // ...optional settings }, }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); // Function for initializating the widget const addInkeepWidget = () => { const search = document.getElementById("search")?.parentNode; if (!search) return; search.style.height = "auto"; search.innerHTML = ""; Inkeep().embed(inkeepConfig(search)); const observer = new MutationObserver((mutationsList, observer) => { const search = document.getElementById("search")?.parentNode; if (!search) return; const inkeepPortals = document.getElementsByTagName("inkeep-portal"); Array.from(inkeepPortals)?.forEach((inkeepPortal) => { inkeepPortal.remove(); }); search.innerHTML = ""; search.style.height = "auto"; inkeepConfig(search).targetElement = search; // Embed the widget using the `Inkeep.embed()` function. Inkeep().embed(inkeepConfig(search)); }); observer.observe(document.head, { childList: true }); }; // Adding the script and initializing the widget const inkeepWidgetScript = document.createElement("script"); inkeepWidgetScript.defer = true; inkeepWidgetScript.innerHTML = addInkeepWidget.toString(); document.body.appendChild(inkeepWidgetScript); const embedScript = addScript( "https://unpkg.com/@inkeep/uikit-js@0.3.18/dist/embed.js" ); embedScript.addEventListener("load", addInkeepWidget); ``` # Add AI Chat to your Remix app ### Define the component Next, create an `InkeepChatButton.tsx` file for the [`Chat Button`](https://docs.inkeep.com/ui-components/react/chat-button) component. # Add AI Search to your Remix app ### Define the component Next, create an `InkeepSearchBar.tsx` file for the [`Search Bar`](https://docs.inkeep.com/ui-components/react/search-bar) component. # Add an AI chatbot to your Slack workspace or community. ## Overview You can add Inkeep as a [Slack](https://slack.com/) app to your Slack community, customer Slack connect channels, or internal channels where your team collaborates. ## Get Workspace ID To configure the bot, you'll need your Slack Workspace ID: 1. Open the Slack client in your [browser](https://app.slack.com/client). 2. Select the target workspace 3. Copy the `{WORKSPACE_ID}` from the browser's navigation bar. The URL will be in the format `https://app.slack.com/client/{WORKSPACE_ID}/{CHANNEL_ID}`. **Workspace ID typically starts with a `T` and is in the format of `TXXXXXXXXXX`.** 4. Alternatively, you can find the ID in the workspace settings. ## Create Integration To use the Inkeep Slack bot, we first need to register it as an integration. 1. Open the [Inkeep Dashboard](https://portal.inkeep.com) 2. Navigate to the **Integrations** tab within the desired project 3. Click on **Create Integration** 4. From the dropdown menu, choose **Slack** 5. Fill in the required fields **Name** and **Workspace ID**. 6. (Optional) If you'd like the bot to only respond to pre-specified channels, deselect the **Is default for workspace** checkbox and add the specific channel IDs in **Enabled for channels** under **Advanced settings** 7. Click on **Create** If you need multiple **@Ask Inkeep** Slack bots for different projects on the same workspace, then specify the channel IDs that it is enabled for. Only one Slack integration can be the `Default` for a given workspace. ## Add to workspace To install the Inkeep Slack bot in your workspace: 1. Click [here](https://install.inkeep.com/ask-slack) 2. Select the desired workspace 3. Confirm the installation. ### Create a channel (optional) If you don't yet have a channel you'd like to add Inkeep to, you can create an `✨ask-ai` or similar channel. Move or pin it to the appropriate sections in your workspace for visibility. This can be for your community members or for your own team members. ### Add to channels Open the workspace you added the **Ask Inkeep** bot to. Navigate to the channel you'd like to add Inkeep to. Type `@Ask Inkeep` with an example question: ``` @Ask Inkeep How do I get started? ``` **Confirm** Slack's prompt to add the bot to the channel. Repeat the steps for any other desired channels. The Slack bot can be added to any type of channel, including private channels or Slack Connect channels with other organizations. To work, the "Ask Inkeep" bot *must* be added as a member of the channel. ### Use as an auto-reply bot Instead of requiring a user to tag the bot with `@Ask Inkeep`, you can configure the Slack bot to automatically reply to all root-level messages in select Slack channels. You can leverage this mode in your Slack community or for internal `#support-triage` channels. To configure: 1. Open the [Inkeep Dashboard](https://portal.inkeep.com) 2. Navigate to the **Integrations** tab within the desired project 3. Select the Integration 4. Expand **Advanced Settings** 5. Configure the following: * **Auto-reply in the below channels**: Specify the channel IDs where you want the bot to auto-reply * **Reply message (optional)**: Enter a custom initial message for the bot to use when it's tagged or auto-replies 6. (Optional) Adjust the tone of the bot by enabling any of these two options: * **AI Draft Mode**: Adjusts bot's tone for generating draft responses for team review. Useful when AI assists team rather than directly interacting with users. * **Human Reviewing Conversations**: Informs users that team members are monitoring conversations. Helps set expectations for response times and accuracy. 6. Click **Save** to apply the changes ## Customize name and icon You can customize the name of the bot and the image used with it. ### Upload an organization avatar (required) 1. Open the [Inkeep Dashboard](https://portal.inkeep.com) 2. Navigate to the **Settings** tab at root of your organization (`https://portal.inkeep.com/{orgAlias}/settings`) 3. Under **Avatar**, select **Choose File** or if there's an existing file, hover over the existing avatar and click **Change** 4. Upload a PNG (preferred) or JPG file you'd like to use as your avatar. Recommended dimension: 512x512. Note that SVGs are not supported by Slack ### Configure the Slack bot name 1. In your Slack app, click on **Add apps** underneath the **Apps** section in your sidebar 2. Select **Ask Inkeep** 3. Click on **Configuration** 4. Scroll to **Bot User** section and click on **Edit** 5. Specify the desired name of the bot, e.g. `Ask CompanyAI` Now, you and your users will be able to tag the bot using this name, e.g. `@Ask CompanyAI`. When typing `@` and in a few other places, Slack will still display the default Inkeep icon. However, any posts made by the bot will show your actual avatar. Your custom name will always be displayed. ## Tag a team member Sometimes, you want users to be able to escalate to a human for help if the user needs additional help or the bot is not able to answer. To make this flow seamless, you can configure your Slack integration to show **Mark as resolved ✅** and **Ask for help 👋** buttons instead of the default 👍 👎. When a user clicks on **Ask for help 👋**, the bot can tag users, user groups, or other bots. To set up: 1. Open the [Inkeep Dashboard](https://portal.inkeep.com) 2. Navigate to the **Integrations** tab within the desired project 3. Select the Slack Integration 4. Expand **Advanced Settings** 5. Under **When a user leaves negative feedback...**, click the dropdown menu 6. Select **Tag a team member** 7. Specify the **User IDs**, **Group IDs**, or **Bot IDs** you'd like to tag You can get the IDs by left-clicking on a profile on Slack, selecting the more options button (often represented as three dots), and clicking on **Copy member ID** or **Copy group ID**. To create a user group, see [this](https://slack.com/help/articles/212906697-Create-a-user-group) guide. We recommend creating a custom bot, e.g. `@Triaging Bot`, that contains the logic you'd like to use for triggering custom down-stream workflows. # Add AI Chat to your Sphinx docs Now, configure the chat button component. ```js addInkeep.js // Embed the widget using the `Inkeep.embed()` function. const inkeepWidget = Inkeep().embed({ componentType: "ChatButton", properties: { chatButtonType: "PILL", baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); ``` # Add AI Search to your Sphinx docs Now, create a container and configure the search bar component. ### Configure the component ```js addInkeep.js // Embed the widget using the `Inkeep.embed()` function. const inkeepWidget = Inkeep().embed({ componentType: "SearchBar", targetElement: document.getElementById("inkeepSearchBar"), properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { // stylesheetUrls: ['/path/to/stylesheets'], // optional // ...optional settings }, }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); ``` ## Container element Create a `searchbox.html` file in `source/_templates` and paste in the below:: This Inkeep `SearchBar` widget will replace the default search bar used on the website. ```html searchbox.html
``` # Add AI Chat to your VitePress docs Now, configure the chat button component. ```js addInkeep.js // Embed the widget using the `Inkeep.embed()` function. const config = { componentType: "ChatButton", // optional -- for syncing UI color mode colorModeSync: { observedElement: document.documentElement, isDarkModeCallback: (el) => { return el.classList.contains("dark"); }, colorModeAttribute: "class", }, properties: { chatButtonType: "PILL", baseSettings: { apiKey: import.meta.env.VITE_INKEEP_API_KEY, // required integrationId: import.meta.env.VITE_INKEEP_INTEGRATION_ID, // required organizationId: import.meta.env.VITE_INKEEP_ORGANIZATION_ID, // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }; const inkeepWidget = Inkeep().embed(config); ``` # Add AI Search to your VitePress docs Now, create a container and configure the search bar component. ### Configure the component Next, embed the widget using the `Inkeep.embed()` function. This will replace the default search bar used on the website. ```js addInkeep.js // Embed the widget using the `Inkeep.embed()` function. const config = { componentType: "SearchBar", targetElement: document.querySelector(".search"), // optional -- for syncing UI color mode colorModeSync: { observedElement: document.documentElement, isDarkModeCallback: (el) => { return el.classList.contains("dark"); }, colorModeAttribute: "class", }, properties: { baseSettings: { apiKey: import.meta.env.VITE_INKEEP_API_KEY, // required integrationId: import.meta.env.VITE_INKEEP_INTEGRATION_ID, // required organizationId: import.meta.env.VITE_INKEEP_ORGANIZATION_ID, // required primaryBrandColor: "#26D6FF", // your brand color, the widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings theme: { // stylesheetUrls: ['/path/to/stylesheets'], // optional // ...optional settings }, }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "/img/logo.svg", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }; const inkeepWidget = Inkeep().embed(config); ``` # Add AI Chat to your VuePress docs Now, configure the chat button component. ```js addInkeep.js const initializeInkeep = () => { const addInkeepWidget = () => { const inkeepWidget = Inkeep().embed({ componentType: "ChatButton", colorModeSync: { observedElement: document.documentElement, isDarkModeCallback: (el) => { const currentTheme = el.getAttribute("data-color-mode"); return currentTheme === "dark"; }, colorModeAttribute: "data-color-mode", }, properties: { chatButtonType: "PILL", baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", // your brand color, widget color scheme is derived from this organizationDisplayName: "Inkeep", // ...optional settings }, modalSettings: { // optional settings }, searchSettings: { // optional settings }, aiChatSettings: { // optional settings botAvatarSrcUrl: "https://mydomain.com/mylogo", // use your own bot avatar quickQuestions: [ "Example question 1?", "Example question 2?", "Example question 3?", ], }, }, }); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", addInkeepWidget); return; } addInkeepWidget(); }; ``` # Add an Embedded Chat to your VuePress docs Add AI chat to your VuePress docs. ## Create container for embedded chat To create a container for the embedded chat component, navigate to your desired page and add the following code: ```jsx
``` ### Configure the component Next, configure the Inkeep widget: ```js addInkeep.js const initializeInkeep = () => { const addInkeepWidget = () => { const embeddedContainer = document.querySelector("#inkeepEmbeddedChat"); if (!embeddedContainer) return; const inkeepWiddget = Inkeep().embed({ componentType: "EmbeddedChat", targetElement: "#inkeepEmbeddedChat", colorModeSync: { observedElement: document.documentElement, isDarkModeCallback: (el) => { const currentTheme = el.getAttribute("data-md-color-scheme"); return currentTheme === "dracula" || currentTheme === "dark"; }, colorModeAttribute: "data-md-color-scheme", }, properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", organizationDisplayName: "Inkeep", }, }, }); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", addInkeepWidget); return; } addInkeepWidget(); }; ``` # Add AI Search to your VuePress docs ## Create container for search bar To create a container for the search bar component, navigate to your desired page and add the following code: ```jsx
``` ### Configure the component Next, configure the Inkeep widget: ```js addInkeep.js const initializeInkeep = () => { const addInkeepWidget = () => { const searchContainer = document.querySelector("#inkeepSearchBar"); if (!searchContainer) return; const inkeepWiddget = Inkeep().embed({ componentType: "SearchBar", targetElement: "#inkeepSearchBar", colorModeSync: { observedElement: document.documentElement, isDarkModeCallback: (el) => { const currentTheme = el.getAttribute("data-md-color-scheme"); return currentTheme === "dracula" || currentTheme === "dark"; }, colorModeAttribute: "data-md-color-scheme", }, properties: { baseSettings: { apiKey: "INKEEP_API_KEY", // required integrationId: "INKEEP_INTEGRATION_ID", // required organizationId: "INKEEP_ORGANIZATION_ID", // required primaryBrandColor: "#26D6FF", organizationDisplayName: "Inkeep", }, }, }); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", addInkeepWidget); return; } addInkeepWidget(); }; ``` # Add AI Chat to your Webflow site ## Initialize the widget Click on the **Footer code** section and add the below script. Press **Save** to apply your settings. # Add a Custom Trigger to your Webflow site ## Create a trigger You need to create a trigger in your application with ID - `inkeepButton`. This can be done using Webflow's built-in **designer**. Example: ## Initialize the widget Click on the **Footer code** section and add the below script. ```html ``` # Add an Embedded AI Chat to your Webflow site ## Create a new page Next, you need to create a new page for the chat widget. You can do this using the built-in Webflow **designer**. ### Add a container to the page Select **Container** from the list of elements and add it to the page: ### Set the container ID Set the ID of the container to `inkeepEmbeddedChat`: ## Configure the widget Go back to **Site settings** and navigate to **Custom code**. Add the below in the **Footer code** section. Press **Save** to apply your settings. As a result, a chat widget will be added to the Ask AI page: # Add AI Search to your Webflow site ## Creating the container Next, you need to add a container for the widget. You can do this using the built-in Webflow **designer**. ### Add the placeholder Add a container to your navigation bar where the search bar will be placed: ### Set the container ID Set the ID of the container to `inkeepSearchBar`: ## Configure the widget Go back to **Site settings** and navigate to **Custom code**. Add the below in the **Footer code** section. ```html ``` Press **Save** to apply your settings. # Add AI Chat to your WordPress site ## Initialize the widget Next, under the **Tools** tab open **Head & Footer Code** option. Add the code below to the **HEAD Code** section: ```html ``` ## Save custom code Click on the **Save Changes**. Navigate to your web application to make sure the widget is installed correctly. # Add AI Search to your WordPress site ## Creating the container Add a container for the widget. You can do this using the built-in WordPress functionality. 1. Open the **Appearance** tab 2. Select **Editor** mode 3. Select the area to which you want to add the **SearchBar** widget 4. Add a **Custom HTML** block Example: ## Initialize the widget Next, under **Tools** go to the **Head & Footer Code** subsection. Add the code below to the **HEAD Code** section: ```html ``` ## Save custom code Click on the **Save Changes**. Navigate to your web application to make sure the widget is installed correctly. # Add AI Chat to your Zendesk help center ## Initialize the widget Next, in the same file, paste the following code: ## Save custom code Click on the **Publish**. Navigate to your web application to make sure the widget is installed correctly. # Add AI Search & Chat to your Zendesk help center ## Creating the container Add a container for the widget: 1. Open the `home_page.hbs` and `section_page.hbs` files 2. Add an element with the **ID** - `inkeepSearchBarNav` and `inkeepSearchBarHero` 3. Apply the below **before** -> **after** changes to `home_page.hbs`: ```html before

{{ t 'search' }}

{{search submit=false instant=settings.instant_search class='search search-full'}}
``` ```html after
```
4. Apply the below **before** -> **after** changes to `section_page.hbs`: ```html before ``` ```html after ``` 5. Click **Publish** ## Custom CSS Zendesk allows you to add a custom css file to the **assets** directory: 1. Create an `inkeep-styles.css` stylesheet and paste in the below:: ```css inkeep-styles.css [data-theme="light"] .ikp-search-bar-trigger__wrapper { background: white; border-radius: 6px; width: 100%; } ``` 2. In the theme code editor, click **Add** 3. Click on **Asset** 4. Upload the created css file ## Initialize the widgets Next, go back to the `header.hbs` file and add the following code at the end of the file: ```html ``` ## Different styles between hero and nav bar You can conditionally apply different stylesheets to the navigation bar or hero search bar. ```js // ... const inkeepSearchBarNavStyles = "{{asset 'inkeep-styles.css'}}"; // <--- should match the uploaded file const inkeepSearchBarHeroStyles = "{{asset 'inkeep-hero-styles.css'}}"; // <--- should match the uploaded file const stylesheetUrls = componentType === "ChatButton" ? [] : targetElementId === "inkeepSearchBarHero" ? [inkeepSearchBarHeroStyles] : [inkeepSearchBarNavStyles]; const inkeepWidget = Inkeep().embed({ componentType, targetElement: targetElementId, properties: { // ...other settings, baseSettings: { // ...other baseSettings, theme: { stylesheetUrls, }, }, }, }); // ... ``` ## Save custom code Click on the **Publish**. Navigate to your web application to make sure the widget is installed correctly. # Embedded Chat Add AI chat to your Zendesk Help Center. ## Creating a custom page 1. Click on **Add** 2. Click on **Custom page** 3. Specify a name for the custom page - `ask-ai` 4. Click on **Add custom page** 5. Add the code below to a new file (`ask_ai.hbs`): ```html ask_ai.hbs
``` ## Custom CSS (optional) Zendesk allows you to add a custom css file to the **assets** directory: 1. Create an `inkeep-styles.css` stylesheet and paste in the below: ```css inkeep-styles.css [data-theme='light'] .ikp-search-bar-trigger__wrapper { background: white; border-radius: 6px; width: 100%; } ``` 2. In the theme code editor, click **Add** 3. Click on **Asset** 4. Upload the created css file ## Initialize the widget Next, go back to the `header.hbs` file and add the following code at the end of the file: ```html ``` ## Save custom code Click on the **Publish**. Navigate to your web application to make sure the widget is installed correctly. # Add AI Search to your Zendesk help center ## Creating the container Add a container for the widget: 1. Open the `home_page.hbs` and `section_page.hbs` files 2. Add an element with the **ID** - `inkeepSearchBarNav` and `inkeepSearchBarHero` 3. Apply the below **before** -> **after** changes to `home_page.hbs`: ```html before

{{ t 'search' }}

{{search submit=false instant=settings.instant_search class='search search-full'}}
``` ```html after
```
4. Apply the below **before** -> **after** changes to `section_page.hbs`: ```html before