docs-v2/assets/js/ask-ai.ts

302 lines
8.4 KiB
TypeScript

import { productData, version } from './page-context.js';
// Type definitions for Kapa.ai widget
declare global {
interface Window {
Kapa: KapaFunction;
influxdatadocs: {
AskAI: typeof AskAI;
};
kapaSettings?: {
user: {
uniqueClientId: string;
email?: string;
};
};
}
}
// eslint-disable-next-line no-unused-vars
type KapaFunction = (command: string, options?: unknown) => void;
// Preinitialize Kapa widget to queue commands before script loads
(function () {
const k = window.Kapa;
if (!k) {
/* eslint-disable no-unused-vars */
interface KapaQueue {
(...args: unknown[]): void;
q?: unknown[][];
c?: (args: unknown[]) => void;
}
/* eslint-enable no-unused-vars */
const i = function (...args: unknown[]) {
if (i.c) {
i.c(args);
}
} as KapaQueue;
i.q = [];
i.c = function (args: unknown[]) {
if (i.q) {
i.q.push(args);
}
};
window.Kapa = i as unknown as KapaFunction;
}
})();
interface ChatAttributes extends Record<string, string | undefined> {
modalExampleQuestions?: string;
sourceGroupIdsInclude?: string;
}
interface InitializeChatParams {
onChatLoad: () => void;
chatAttributes: ChatAttributes;
}
interface AskAIParams {
userid?: string;
email?: string;
onChatLoad?: () => void;
[key: string]: unknown;
}
function setUser(userid: string, email?: string): void {
const NAMESPACE = 'kapaSettings';
// Set the user ID and email in the global settings namespace.
// The chat widget will use this on subsequent chats to personalize
// the user's experience.
window[NAMESPACE] = {
user: {
uniqueClientId: userid,
...(email && { email }),
},
};
}
// Initialize the chat widget
function initializeChat({
onChatLoad,
chatAttributes,
}: InitializeChatParams): void {
/* See https://docs.kapa.ai/integrations/website-widget/configuration for
* available configuration options.
* All values are strings.
*/
// If you make changes to data attributes here, you also need to
// port the changes to the api-docs/template.hbs API reference template.
const requiredAttributes = {
websiteId: 'a02bca75-1dd3-411e-95c0-79ee1139be4d',
projectName: 'InfluxDB',
projectColor: '#020a47',
projectLogo: '/img/influx-logo-cubo-white.png',
};
const optionalAttributes = {
modalDisclaimer:
'This AI can access [documentation for InfluxDB, clients, and related tools](https://docs.influxdata.com). Information you submit is used in accordance with our [Privacy Policy](https://www.influxdata.com/legal/privacy-policy/).',
modalExampleQuestions:
'Use Python to write data to InfluxDB 3,How do I query using SQL?,How do I use MQTT with Telegraf?',
buttonHide: 'true',
exampleQuestionButtonWidth: 'auto',
modalOpenOnCommandK: 'true',
modalExampleQuestionsColSpan: '8',
modalFullScreenOnMobile: 'true',
modalHeaderPadding: '.5rem',
modalInnerPositionRight: '0',
modalInnerPositionLeft: '',
modalLockScroll: 'false',
modalOverrideOpenClassAskAi: 'ask-ai-open',
modalSize: '640px',
modalWithOverlay: 'false',
modalYOffset: '10vh',
userAnalyticsFingerprintEnabled: 'true',
fontFamily: 'Proxima Nova, sans-serif',
modalHeaderBgColor: 'linear-gradient(90deg, #d30971 0%, #9b2aff 100%)',
modalHeaderBorderBottom: 'none',
modalTitleColor: '#fff',
modalTitleFontSize: '1.25rem',
};
const scriptUrl = 'https://widget.kapa.ai/kapa-widget.bundle.js';
const script = document.createElement('script');
script.async = true;
script.src = scriptUrl;
script.onload = function () {
onChatLoad();
window.influxdatadocs.AskAI = AskAI;
};
script.onerror = function () {
console.error('Error loading AI chat widget script');
};
const dataset = {
...requiredAttributes,
...optionalAttributes,
...chatAttributes,
};
Object.keys(dataset).forEach((key) => {
// Assign dataset attributes from the object
const value = dataset[key as keyof typeof dataset];
if (value !== undefined) {
script.dataset[key] = value;
}
});
// Check for an existing script element to remove
const oldScript = document.querySelector(`script[src="${scriptUrl}"]`);
if (oldScript) {
oldScript.remove();
}
document.head.appendChild(script);
}
function getVersionSpecificConfig(configKey: string): unknown {
// Try version-specific config first (e.g., ai_sample_questions__v1)
if (version && version !== 'n/a') {
const versionKey = `${configKey}__v${version}`;
const versionConfig = productData?.product?.[versionKey];
if (versionConfig) {
return versionConfig;
}
}
// Fall back to default config
return productData?.product?.[configKey];
}
function getProductExampleQuestions(): string {
const questions = getVersionSpecificConfig('ai_sample_questions') as
| string[]
| undefined;
if (!questions || questions.length === 0) {
return '';
}
// Only add version hints for InfluxDB database products
// Other tools like Explorer, Telegraf, Chronograf, Kapacitor,
// Flux don't need version hints
const productNamespace = productData?.product?.namespace;
const shouldAddVersionHint =
productNamespace === 'influxdb' ||
productNamespace === 'influxdb3' ||
productNamespace === 'enterprise_influxdb';
if (!shouldAddVersionHint) {
return questions.join(',');
}
const productName = productData?.product?.name || 'InfluxDB';
// Append version hint to each question
const questionsWithHint = questions.map((question) => {
return `${question} My version: ${productName}`;
});
return questionsWithHint.join(',');
}
function getProductSourceGroupIds(): string {
const sourceGroupIds = getVersionSpecificConfig('ai_source_group_ids') as
| string
| undefined;
return sourceGroupIds || '';
}
function getVersionContext(): string {
// Only add version context for InfluxDB database products
const productNamespace = productData?.product?.namespace;
const shouldAddVersionContext =
productNamespace === 'influxdb' ||
productNamespace === 'influxdb3' ||
productNamespace === 'enterprise_influxdb';
if (!shouldAddVersionContext) {
return '';
}
const productName = productData?.product?.name || 'InfluxDB';
return `My version: ${productName}`;
}
function setupVersionPrefill(): void {
const versionContext = getVersionContext();
if (!versionContext) {
return;
}
// Wait for Kapa to be available
const checkKapa = (): void => {
if (!window.Kapa || typeof window.Kapa !== 'function') {
setTimeout(checkKapa, 100);
return;
}
// Find the Ask AI button and add click handler to prefill version
const askAIButton = document.querySelector('.ask-ai-open');
if (askAIButton) {
askAIButton.addEventListener(
'click',
(e) => {
e.preventDefault();
e.stopPropagation();
window.Kapa('open', { query: versionContext });
},
true
); // Use capture phase to run before Kapa's handler
}
// Listen for conversation reset to re-fill version context
window.Kapa('onAskAIConversationReset', () => {
// Small delay to ensure the reset is complete
setTimeout(() => {
window.Kapa('open', { query: versionContext });
}, 100);
});
};
checkKapa();
}
/**
* Initialize the Ask AI chat widget with version-aware source filtering
*
* @param params - Configuration parameters
* @param params.userid - Optional unique user ID
* @param params.email - Optional user email
* @param params.onChatLoad - Optional callback when chat widget loads
* @param params.chatParams - Additional Kapa widget configuration attributes
*/
export default function AskAI({
userid,
email,
onChatLoad,
...chatParams
}: AskAIParams): void {
const modalExampleQuestions = getProductExampleQuestions();
const sourceGroupIds = getProductSourceGroupIds();
const chatAttributes: ChatAttributes = {
...(modalExampleQuestions && { modalExampleQuestions }),
...(sourceGroupIds && { sourceGroupIdsInclude: sourceGroupIds }),
...(chatParams as Record<string, string>),
};
const wrappedOnChatLoad = (): void => {
// Setup version pre-fill after widget loads
setupVersionPrefill();
// Call original onChatLoad if provided
if (onChatLoad) {
onChatLoad();
}
};
initializeChat({ onChatLoad: wrappedOnChatLoad, chatAttributes });
if (userid) {
setUser(userid, email);
}
}