diff --git a/assets/js/ask-ai.js b/assets/js/ask-ai.js deleted file mode 100644 index 120047029..000000000 --- a/assets/js/ask-ai.js +++ /dev/null @@ -1,108 +0,0 @@ -import { productData } from './page-context.js'; - -function setUser(userid, email) { - 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 }) { - /* 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 - script.dataset[key] = dataset[key]; - }); - - // Check for an existing script element to remove - const oldScript = document.querySelector(`script[src="${scriptUrl}"]`); - if (oldScript) { - oldScript.remove(); - } - document.head.appendChild(script); -} - -function getProductExampleQuestions() { - const questions = productData?.product?.ai_sample_questions; - return questions?.join(',') || ''; -} - -/** - * chatParams: specify custom (for example, page-specific) attribute values for the chat, pass the dataset key-values (collected in ...chatParams). See https://docs.kapa.ai/integrations/website-widget/configuration for available configuration options. - * onChatLoad: function to call when the chat widget has loaded - * userid: optional, a unique user ID for the user (not currently used for public docs) - */ -export default function AskAI({ userid, email, onChatLoad, ...chatParams }) { - const modalExampleQuestions = getProductExampleQuestions(); - const chatAttributes = { - ...(modalExampleQuestions && { modalExampleQuestions }), - ...chatParams, - }; - initializeChat({ onChatLoad, chatAttributes }); - - if (userid) { - setUser(userid, email); - } -} diff --git a/assets/js/ask-ai.ts b/assets/js/ask-ai.ts new file mode 100644 index 000000000..ef79470ee --- /dev/null +++ b/assets/js/ask-ai.ts @@ -0,0 +1,330 @@ +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 @typescript-eslint/no-unused-vars, no-unused-vars +type KapaFunction = (command: string, options?: unknown) => void; + +interface ChatAttributes extends Record { + 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(','); + } + + // Extract version subpath for hint + const pathParts = window.location.pathname.split('/').filter(Boolean); + const versionPath = + pathParts.length >= 2 + ? `/${pathParts[0]}/${pathParts[1]}/` + : window.location.pathname; + + // Append version hint to each question + const questionsWithHint = questions.map((question) => { + return `${question} (Version: ${versionPath})`; + }); + + 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 ''; + } + + // Extract version subpath for context + const pathParts = window.location.pathname.split('/').filter(Boolean); + const versionPath = + pathParts.length >= 2 + ? `/${pathParts[0]}/${pathParts[1]}/` + : window.location.pathname; + + return `(Version: ${versionPath})`; +} + +function setupVersionPrefill(): void { + const versionContext = getVersionContext(); + if (!versionContext) { + console.log('[AskAI] No version context needed'); + return; + } + + console.log('[AskAI] Version context:', versionContext); + + // Wait for Kapa to be available + const checkKapa = (): void => { + if (!window.Kapa || typeof window.Kapa !== 'function') { + console.log('[AskAI] Waiting for Kapa...'); + setTimeout(checkKapa, 100); + return; + } + + console.log('[AskAI] Kapa found (preinitialized)'); + + // Use Kapa event system to intercept modal opens + window.Kapa('onModalOpen', () => { + console.log('[AskAI] Modal opened'); + + // Try multiple times with different delays to find the textarea + const trySetValue = (attempt = 0): void => { + console.log(`[AskAI] Attempt ${attempt + 1} to find textarea`); + + // Try multiple selectors + const selectors = [ + 'textarea[placeholder*="Ask"]', + 'textarea[placeholder*="ask"]', + 'textarea', + '#kapa-widget-container textarea', + '[data-kapa-widget] textarea', + ]; + + let textarea: HTMLTextAreaElement | null = null; + for (const selector of selectors) { + textarea = document.querySelector(selector); + if (textarea) { + console.log(`[AskAI] Found textarea with selector: ${selector}`); + break; + } + } + + if (textarea) { + // Check if it already has a value + if (!textarea.value || textarea.value.trim() === '') { + console.log('[AskAI] Setting textarea value to:', versionContext); + textarea.value = versionContext; + + // Dispatch multiple events to ensure React picks it up + const inputEvent = new Event('input', { bubbles: true }); + const changeEvent = new Event('change', { bubbles: true }); + textarea.dispatchEvent(inputEvent); + textarea.dispatchEvent(changeEvent); + + // Focus at the beginning so user can start typing + textarea.setSelectionRange(0, 0); + textarea.focus(); + + console.log('[AskAI] Version context added to input'); + } else { + console.log('[AskAI] Textarea already has value:', textarea.value); + } + } else if (attempt < 5) { + // Try again with increasing delays + const delay = (attempt + 1) * 100; + console.log(`[AskAI] Textarea not found, retrying in ${delay}ms`); + setTimeout(() => trySetValue(attempt + 1), delay); + } else { + console.log('[AskAI] Failed to find textarea after 5 attempts'); + } + }; + + trySetValue(); + }); + + console.log('[AskAI] Version pre-fill setup complete'); + }; + + 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), + }; + + 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); + } +} diff --git a/data/products.yml b/data/products.yml index 271a86df5..fbd2e9fbd 100644 --- a/data/products.yml +++ b/data/products.yml @@ -23,9 +23,10 @@ influxdb3_core: x-influxdb-build: 'Core' url_contains: ['localhost:8181'] ai_sample_questions: - - How do I install and run InfluxDB 3 Core? - - How do I write a plugin for the Python Processing engine? - - How do I write data using the HTTP API for InfluxDB 3 Core? + - How do I install and run the database? + - Help me write a plugin for the Python Processing engine + - How do I write data using the HTTP API? + ai_source_group_ids: "b650cf0b-4b52-42e8-bde7-a02738f27262" influxdb3_enterprise: name: InfluxDB 3 Enterprise @@ -52,9 +53,10 @@ influxdb3_enterprise: x-influxdb-build: 'Enterprise' url_contains: ['localhost:8181'] ai_sample_questions: - - How do I install and run InfluxDB 3 Enterprise? - - Help me write a plugin for the Python Processing engine? - - How do I start a read replica node with InfluxDB 3 Enterprise? + - How do I install and run the database? + - Help me write a plugin for the Python Processing engine + - How do I start a read replica node? + ai_source_group_ids: "b650cf0b-4b52-42e8-bde7-a02738f27262" influxdb3_explorer: name: InfluxDB 3 Explorer @@ -66,9 +68,10 @@ influxdb3_explorer: latest_patch: 1.4.0 placeholder_host: localhost:8888 ai_sample_questions: - - How do I query data using InfluxDB 3 Explorer? - - How do I use InfluxDB 3 Explorer to visualize data? - - How do I install InfluxDB 3 Explorer? + - How do I query data? + - How do I visualize data? + - How do I install and run? + ai_source_group_ids: "b650cf0b-4b52-42e8-bde7-a02738f27262" influxdb3_cloud_serverless: name: InfluxDB Cloud Serverless @@ -94,8 +97,9 @@ influxdb3_cloud_serverless: detection: url_contains: ['us-east-1-1.aws.cloud2.influxdata.com', 'eu-central-1-1.aws.cloud2.influxdata.com'] ai_sample_questions: - - How do I migrate from InfluxDB Cloud 2 to InfluxDB Cloud Serverless? - - What tools can I use to write data to InfluxDB Cloud Serverless? + - How do I migrate from Cloud (TSM)? + - What tools can I use to write data? + ai_source_group_ids: "b650cf0b-4b52-42e8-bde7-a02738f27262" influxdb3_cloud_dedicated: name: InfluxDB Cloud Dedicated @@ -120,9 +124,10 @@ influxdb3_cloud_dedicated: detection: url_contains: ['influxdb.io'] ai_sample_questions: - - How do I migrate from InfluxDB v1 to InfluxDB Cloud Dedicated? - - What tools can I use to write data to Cloud Dedicated? - - How do I use SQL and parameterized queries with Cloud Dedicated? + - How do I migrate from v1? + - What tools can I use to write data? + - How do I use SQL and parameterized queries? + ai_source_group_ids: "b650cf0b-4b52-42e8-bde7-a02738f27262" influxdb3_clustered: name: InfluxDB Clustered @@ -147,9 +152,10 @@ influxdb3_clustered: ping_headers: x-influxdb-version: 'influxqlbridged-development' ai_sample_questions: - - How do I use a Helm chart to configure Clustered? - - What tools can I use to write data to Clustered? - - How do I use SQL and parameterized queries with InfluxDB Clustered? + - How do I use a Helm chart to configure the cluster? + - What tools can I use to write data? + - How do I use SQL and parameterized queries? + ai_source_group_ids: "b650cf0b-4b52-42e8-bde7-a02738f27262" influxdb: name: InfluxDB @@ -183,9 +189,15 @@ influxdb: x-influxdb-version: '^(1|2)\.' url_contains: ['localhost:8086'] ai_sample_questions: - - How do I write and query data with InfluxDB v2 OSS? - - How can I migrate from InfluxDB v2 OSS to InfluxDB 3 Core? - - How do I manage auth tokens in InfluxDB v2 OSS? + - How do I write and query data? + - How can I migrate to InfluxDB 3? + - How do I manage auth tokens? + ai_source_group_ids: "3e905caa-dd6f-464b-abf9-c3880e09f128" + ai_sample_questions__v1: + - How do I query data with InfluxQL? + - How do I set up continuous queries? + - How do I manage retention policies? + ai_source_group_ids__v1: "d809f67b-867d-4f17-95f0-c33dbadbf15f" influxdb_cloud: name: InfluxDB Cloud (TSM) @@ -208,9 +220,10 @@ influxdb_cloud: detection: url_contains: ['us-west-2-1.aws.cloud2.influxdata.com', 'us-west-2-2.aws.cloud2.influxdata.com', 'us-east-1-1.aws.cloud2.influxdata.com', 'eu-central-1-1.aws.cloud2.influxdata.com', 'us-central1-1.gcp.cloud2.influxdata.com', 'westeurope-1.azure.cloud2.influxdata.com', 'eastus-1.azure.cloud2.influxdata.com'] ai_sample_questions: - - How do I write and query data with InfluxDB Cloud 2? - - How is Cloud 2 different from Cloud Serverless? - - How do I manage auth tokens in InfluxDB Cloud 2? + - How do I write and query data? + - How is Cloud (TSM) different from Cloud Serverless? + - How do I manage auth tokens? + ai_source_group_ids: "3e905caa-dd6f-464b-abf9-c3880e09f128" telegraf: name: Telegraf @@ -276,9 +289,10 @@ enterprise_influxdb: ping_headers: x-influxdb-build: 'Enterprise' ai_sample_questions: - - How can I configure my InfluxDB v1 Enterprise server? - - How do I replicate data between InfluxDB v1 Enterprise and OSS? - - How do I query data stored in InfluxDB v1? + - How can I configure the server? + - How do I replicate data between Enterprise and OSS? + - How do I query data? + ai_source_group_ids: "d809f67b-867d-4f17-95f0-c33dbadbf15f" flux: name: Flux @@ -288,6 +302,7 @@ flux: versions: [v0] latest: v0.x ai_sample_questions: - - How do I write a Flux query for InfluxDB v2 (TSM)? - - How do I use Flux to transform data stored in InfluxDB v2? + - How do I write a Flux query? + - How do I use Flux to transform data? - How do I join data with Flux? + ai_source_group_ids: "d809f67b-867d-4f17-95f0-c33dbadbf15f,3e905caa-dd6f-464b-abf9-c3880e09f128"