Skip to content

Integrating SDK with Native JS

The native JS SDK provides UI, functionality, and integration similar to iframe, allowing you to directly use the HENGSHI ChatBI conversation components, styles, and features. You only need to include the AI assistant's JS file in your HTML file (or Vue/React component) and call the relevant APIs. This will require you to have some JavaScript development experience.

Quick Start

  1. Obtain the JS SDK Link

Navigate to the Settings -> Feature Configuration -> Intelligent Query Assistant -> Console page within the HENGSHI system. Click Options -> Use SDK Integration to open the integration guide page. In the drawer popup, you will find the SDK link for integration, such as:

https://develop.hengshi.org/assets/hengshi-copilot@<version>.js

Note

@<version> is the SDK version number. Please ensure that the SDK version you use matches your system version. You will also need to update it when the system is upgraded.

  1. Import the JS SDK

We provide sample code for integrating the SDK into front-end frameworks such as Vue, React, and plain JS. You can choose based on your needs. This requires you to have some front-end development experience.

js
<!DOCTYPE html>
<html lang="en-US">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI Assistant</title>
    <script type="text/javascript" src="https://develop.hengshi.org/assets/hengshi-copilot@<version>.js" defer></script>
</head>
<body>
    <!-- Add a button at an appropriate location in your project -->
    <button id="trigger-ai">Launch AI Assistant</button>
    <!-- Add the container required by the SDK at an appropriate location in your project -->
    <div id="copilot-root" style="display: none;"></div>

    <script>
        const copilotConfig = { // Define AI Assistant configuration
            locale: 'en-US',
            draggable: {
                enable: true,
                minWidth: 440,
                minHeight: 440,
                position: {
                    x: window.innerWidth - 480,
                    y: 20,
                },
                size: {
                    width: 440,
                    height: window.innerHeight * 0.8,
                },
                onDragStop: onDragStop,
                onResize: onResize,
            },
            userConfig: {
                dataAppId: 130870,
                datasetId: 1,
            },
        };

        let copilotInstance = null;
        const button = document.getElementById('trigger-ai');
        const copilotRoot = document.getElementById('copilot-root');

        button.addEventListener('click', toggleCopilot);

        function onDragStop(event, position) {
            copilotConfig.draggable.position = position;
        }

        function onResize(event, position, size) {
            copilotConfig.draggable.position = position;
            copilotConfig.draggable.size = size;
        }

        function launchCopilot() {
            if (typeof Copilot === 'undefined') {
                (function(fn) {
                    fn(launchCopilot);
                })(requestIdleCallback || setTimeout);
            } else {
                if (!copilotInstance) {
                    copilotInstance = new Copilot(copilotRoot, copilotConfig);
                    copilotRoot.style.display = 'block';
                    button.textContent = 'Close AI Assistant';
                } else {
                    copilotInstance.show(); // Use show method to display existing instance
                }
            }
        }

        function destroyCopilot() {
            if (copilotInstance) {
                copilotInstance.hide(); // Use hide method to hide instance instead of destroying
                copilotInstance = null;
                copilotRoot.style.display = 'none';
                button.textContent = 'Launch AI Assistant';
            }
        }

        function toggleCopilot() {
            if (copilotInstance) {
                destroyCopilot();
            } else {
                launchCopilot();
            }
        }
    </script>
</body>
</html>
jsx
import React, { useState, useEffect } from 'react';

const CopilotComponent = () => {
  const [copilotInstance, setCopilotInstance] = useState(null);
  const [buttonLabel, setButtonLabel] = useState('Launch AI Assistant');

  const copilotConfig = {
    locale: 'en-US',
    draggable: {
      enable: true,
      minWidth: 440,
      minHeight: 440,
      position: {
        x: window.innerWidth - 480,
        y: 20,
      },
      size: {
        width: 440,
        height: window.innerHeight * 0.8,
      },
      onDragStop: onDragStop,
      onResize: onResize,
    },
    userConfig: {
      dataAppId: 130870,
      datasetId: 1,
    },
  };

  useEffect(() => {
    // Load SDK
    const script = document.createElement('script');
    script.src = 'https://develop.hengshi.org/assets/hengshi-copilot@<version>.js';
    script.async = true;
    document.body.appendChild(script);

    return () => {
      document.body.removeChild(script); // Clean up loaded script
    };
  }, []);

  function onDragStop(event, position) {
    copilotConfig.draggable.position = position;
  }

  function onResize(event, position, size) {
    copilotConfig.draggable.position = position;
    copilotConfig.draggable.size = size;
  }

  const launchCopilot = () => {
    if (typeof Copilot === 'undefined') {
      (function(fn) {
        fn(launchCopilot);
      })(requestIdleCallback || setTimeout);
    } else {
      if (!copilotInstance) {
        const copilotRoot = document.getElementById('copilot-root');
        const instance = new Copilot(copilotRoot, copilotConfig);
        setCopilotInstance(instance);
        setButtonLabel('Close AI Assistant');
      } else {
        copilotInstance.show(); // Use show method to display existing instance
      }
    }
  };

  const destroyCopilot = () => {
    if (copilotInstance) {
      copilotInstance.hide(); // Use hide method to hide instance instead of destroying
      setCopilotInstance(null);
      setButtonLabel('Launch AI Assistant');
    }
  };

  const toggleCopilot = () => {
    if (copilotInstance) {
      destroyCopilot();
    } else {
      launchCopilot();
    }
  };

  return (
    <div>
      <button onClick={toggleCopilot}>{buttonLabel}</button>
      {copilotInstance && <div id="copilot-root"></div>}
    </div>
  );
};

export default CopilotComponent;
vue
<template>
  <div>
    <button @click="toggleCopilot">{{ buttonLabel }}</button>
    <div id="copilot-root" v-if="copilotInstance"></div>
  </div>
</template>

<script>
import { ref, onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    const copilotInstance = ref(null);
    const buttonLabel = ref('Launch AI Assistant');

    const copilotConfig = {
      locale: 'en-US',
      draggable: {
        enable: true,
        minWidth: 440,
        minHeight: 440,
        position: {
          x: window.innerWidth - 480,
          y: 20,
        },
        size: {
          width: 440,
          height: window.innerHeight * 0.8,
        },
        onDragStop: onDragStop,
        onResize: onResize,
      },
      userConfig: {
        dataAppId: 130870,
        datasetId: 1,
      },
    };

    function onDragStop(event, position) {
      copilotConfig.draggable.position = position;
    }

    function onResize(event, position, size) {
      copilotConfig.draggable.position = position;
      copilotConfig.draggable.size = size;
    }

    function launchCopilot() {
      if (typeof Copilot === 'undefined') {
        (function(fn) {
          fn(launchCopilot);
        })(requestIdleCallback || setTimeout);
      } else {
        if (!copilotInstance.value) {
          copilotInstance.value = new Copilot(document.getElementById('copilot-root'), copilotConfig);
          buttonLabel.value = 'Close AI Assistant';
        } else {
          copilotInstance.value.show(); // Use show method to display existing instance
        }
      }
    }

    function destroyCopilot() {
      if (copilotInstance.value) {
        copilotInstance.value.hide(); // Use hide method to hide instance instead of destroying
        copilotInstance.value = null;
        buttonLabel.value = 'Launch AI Assistant';
      }
    }

    function toggleCopilot() {
      if (copilotInstance.value) {
        destroyCopilot();
      } else {
        launchCopilot();
      }
    }

    return {
      copilotInstance,
      buttonLabel,
      toggleCopilot,
    };
  },
};
</script>

<style>
/* Add styles (if needed) */
</style>
  1. Start/Open your project and check if the integration is successful.

JS SDK Instance

The result of new Copilot(...) is a copilot instance, which you can use to operate Copilot.

js
{
  copilotConfig: {
    config: {...}, // Large model configuration information
    loading: false, // Whether it is loading
    onVerifyConfig: () => {}, // Verify configuration information
    onChangeProvider: () => {}, // Switch model provider
    // ...
  },
  context: {
    api: {...},
    endpoints: {...},
    // Copilot state
    isLoading: false,
    // Current session list, data format reference
    // https://api.hengshi.com/conversation.html#conversation
    conversations: [],
    setConversations: () => {},
    currentConversationId: null,
    setCurrentConversationId: () => {},
    runningChatUid: null,
    setRunningChatUid: () => {},
    setChat: (id, chatUid, chat) => {},
    // Fetch historical session list
    onFetchConversations: (offset = 0, limit = 10) => {},
    // Create session
    onCreateConversation: prompt => {},
    onFetchConversation: id => {},
    onClearConversations: () => {},
    onFetchPreviousConversations: () => {},
    // Create Q&A
    onCreateChat: body => {},
    onDeleteChat: (id, chatUid) => {},
    onFetchSuggestions: refresh => {},
    onFetchFavorites: () => {},
    onCancelChatFavorite: (id, chatUid) => {},
  },
  // Control dialog visibility
  show: () => {}, // Show the dialog
  hide: () => {}, // Hide the dialog
}

JS SDK Instance Configuration Parameters

The configuration parameters for new Copilot(...) include the following main sections:

js
const copilotConfig = {
  locale: 'en-US', // Language setting
  draggable: { // Dialog dragging configuration
    enable: true,
    minWidth: 440,
    minHeight: 440,
    position: {
      x: window.innerWidth - 480,
      y: 20,
    },
    size: {
      width: 440,
      height: window.innerHeight * 0.8,
    },
    onDragStop: onDragStop,
    onResize: onResize,
  },
  userConfig: { // Data source configuration
    dataAppId: 130870,
    datasetId: 1,
  },
  completionConfig: { // AI completion configuration
    meta: 'answer,chart,chartConfig', // Specify content combination to render
    openChartConfig: false, // Whether to expand the chart logic panel, defaults to true
    onCompletionDone: (conversationId, uid) => {
      // AI completion callback
      // conversationId: Current conversation ID
      // uid: Current completion unique identifier
      console.log('AI completion done:', conversationId, uid);
    },
  },
};

userConfig Data Source Configuration

userConfig is used to configure data sources, supporting multiple data source configurations. The data source configuration supports two modes, but only one mode can be used within a single configuration:

  1. Data Package and Dataset Mode:
  • dataSources: Array containing multiple data source configuration objects
    • dataAppId: Data package ID
    • datasetId: Dataset ID

Example:

js
userConfig: {
  dataSources: [
    {
      dataAppId: 130870,
      datasetId: 1,
    },
    {
      dataAppId: 130871,
      datasetId: 2,
    }
  ]
}
  1. Business Indicator Subject Domain Mode:
  • dataSources: Array containing multiple subject domain configuration objects
    • subjectId: Subject domain ID

Example:

js
userConfig: {
  dataSources: [
    {
      subjectId: 1,
    },
    {
      subjectId: 2,
    }
  ]
}

Note: Each dataSources configuration can only use one mode, meaning you must either use all dataAppId + datasetId combinations or all subjectId. You cannot mix both modes in the same configuration.

i18n Localization Configuration

The i18n configuration is used to customize interface text, supporting multiple languages:

  • Organized by language codes (e.g., 'en-US', 'zh-CN')
  • Multiple text key-value pairs can be configured for each language
  • Predefined text keys include:
    • copilot.question-reasoning: Question understanding section title
    • copilot.question-answer: Data performance section title
    • copilot.question-reasoning-logic: Data logic section title

Example:

js
i18n: {
  "en-US": {
    'copilot.question-reasoning': 'Question Understanding',
    'copilot.question-answer': 'Data Performance',
    'copilot.question-reasoning-logic': 'Data Logic'
  },
  "zh-CN": {
    'copilot.question-reasoning': '问题理解',
    'copilot.question-answer': '数据表现',
    'copilot.question-reasoning-logic': '取数逻辑'
  }
}

stylesheet Custom Styling

The stylesheet option is used to customize the interface appearance:

  • Accepts a CSS string
  • Can be retrieved from a <style> tag definition
  • Supports CSS custom properties (variables)
  • Main style variables:
    • --brand: Theme color

Example: Defining styles through a style tag

html
<style id="copilot-css">
  /* Change the main theme color */
  :host > *, ::before, ::after {
    --brand: #4CAF50;
  }
</style>
<script>
  const css = document.querySelector('#copilot-css');
  const style = css.textContent;

  // Use in Copilot configuration
  const copilot = new window.Copilot({
    // Other config...
    stylesheet: style,
  });

  // Or use in completion configuration
  window.Copilot.renderChat(container, {
    // Other config...
    stylesheet: style,
  });
</script>

These styling options can be used in both the main Copilot configuration and the completion configuration, allowing for consistent or distinct styling in different contexts.

completionConfig Configuration Description

completionConfig controls the completion behavior and display mode of AI conversations:

  • meta: String, specifies the content to render, can be a combination of fields like refineQuestion, answer, chart, and chartConfig
  • openChartConfig: Boolean, controls whether to expand the chart logic panel, defaults to true
  • onCompletionDone: Function, callback triggered when AI completion is done, receives two parameters:
    • conversationId: Current conversation ID
    • uid: Current completion unique identifier

1. Controlling Dialog Visibility

The Copilot instance provides show and hide methods to control the dialog's visibility, which is more efficient than completely destroying and recreating the instance:

js
// Create a Copilot instance
const copilotInstance = new Copilot(copilotRoot, copilotConfig);

// Hide the dialog (doesn't destroy the instance, just hides the UI)
copilotInstance.hide();

// Show the dialog
copilotInstance.show();

// Toggle show/hide status
function toggleCopilot() {
  if (copilotInstance) {
    // You can check the DOM element state to determine current visibility
    const isVisible = copilotRoot.style.display !== 'none';
    if (isVisible) {
      copilotInstance.hide();
      button.textContent = 'Launch AI Assistant';
    } else {
      copilotInstance.show();
      button.textContent = 'Close AI Assistant';
    }
  }
}

Compared to completely destroying and recreating the instance, using the show/hide methods has the following advantages:

  • Maintains conversation context and state
  • Faster response time (no need to reinitialize)
  • Lower resource consumption

2. Calling HTTP API via JS

In the copilot example above, the api object provides all the methods for calling HTTP APIs. You can interact with the backend by invoking these methods, such as:

2.1 Create a conversation

js
const { body: { data: conversation }} = await copilot.api.requestCreateConversation({
  body: {
    title: 'This is the title of the conversation',
  },
}).value;
Data Structure
js
conversation = {
  "id": 135,
  "title": "This is the title of the conversation",
  "createdBy": 268,
  "createdAt": "2024-09-14 15:47:23",
  "updatedBy": 268,
  "updatedAt": "2024-09-14 15:47:23"
}

2.2. Create a Q&A

js
const { body: { data: chat }} = await copilot.api.requestCreateChat({
  id: conversation.id,
  body: {
    prompt: 'Which director has the highest-grossing movies?', // This is the user's question
    userConfig: { // Refer to the userConfig in copilotConfig above
      dataAppId: 129150,
      datasetId: 1,
    }
  },
  qs: {
    sync: true, // Whether to execute synchronously
    timeout: 1000 * 60, // Timeout duration in milliseconds
  },
}).value;
Data Structure
js
chat = {
    "conversationId": 135,
    "prompt": "Which director has the highest-grossing movies?",
    "answer": "Christopher Nolan's movies have the highest box office earnings.",
    "model": "gpt-4o",
    "uid": "08ff93ca-1972-4916-b884-35fab6f91c64",
    "temperature": 0,
    "createdBy": 268,
    "createdAt": "2024-09-14 15:47:23",
    "updatedBy": 268,
    "updatedAt": "2024-09-14 15:47:23",
    "responseAt": "2024-09-14 15:47:30",
    "isDelete": false,
    "isContextEnd": false,
    "suggestQuestions": [],
    "statusList": [
        "PENDING",
        "ANALYZE_REQUEST",
        "HQL_SELECT_FIELDS",
        "HQL_SELECT_FUNCTIONS",
        "GENERATE_HQL_QUERY",
        "DOING_HQL_QUERY",
        "DOING_SUMMARY",
        "DOING_QUESTION_SUGGESTING",
        "DONE"
    ],
    "userConfig": {
        "datasetId": 1,
        "dataAppId": 129150
    },
    "autoConfig": {
        "agentType": "HQL_AGENT"
    },
    "isCurrent": true,
    "usage": [
        {
            "completion_tokens": 24,
            "prompt_tokens": 4063,
            "total_tokens": 4087
        },
        {
            "completion_tokens": 5,
            "prompt_tokens": 1424,
            "total_tokens": 1429
        },
        {
            "completion_tokens": 49,
            "prompt_tokens": 3906,
            "total_tokens": 3955
        },
        {
            "completion_tokens": 16,
            "prompt_tokens": 299,
            "total_tokens": 315
        }
    ],
    "chartCreatedAt": "2024-09-14 15:47:30",
    "refineQuestion": "Which director has the highest-grossing movies?",
    "favorite": false,
    "charts": [
        {
            "appId": 129150,
            "datasetId": 1,
            "options": {
                "axes": [
                    {
                        "op": "{{1}}.{director}",
                        "uid": "uid1",
                        "fieldName": "director",
                        "kind": "formula",
                        "datasetId": 1,
                        "labelOrigin": "director",
                        "label": "director",
                        "isAggregate": false,
                        "value": "{{coffe_product_table}}.{director}",
                        "dataset": 1,
                        "fieldType": "string"
                    },
                    {
                        "op": "max({{1}}.{votes})",
                        "uid": "uid2",
                        "fieldName": "votes",
                        "kind": "formula",
                        "datasetId": 1,
                        "labelOrigin": "votes",
                        "label": "votes",
                        "isAggregate": true,
                        "value": "max({{coffe_product_table}}.{votes})",
                        "dataset": 1,
                        "fieldType": "number"
                    }
                ],
                "name": "Bar",
                "limit": 1,
                "sort": [
                    {
                        "op": "uid2",
                        "kind": "reference",
                        "direction": "desc"
                    },
                    {
                        "op": "uid1",
                        "kind": "reference",
                        "direction": "asc"
                    }
                ]
            },
            "dataAppId": 129150,
            "datasetIds": [
                1
            ]
        }
    ]
}

Using Copilot to Embed Data Analysis Capabilities in Existing AI Chat Tools

In addition to embedding the full AI assistant chat functionality, the SDK provides a static method renderChat that allows you to integrate HENGSHI's data analysis capabilities into your own LLM chat tools. It supports two usage modes: dynamic query mode based on prompt and static rendering mode.

Dynamic Query Mode

In this mode, you only need to provide the user's question (prompt), and the SDK will automatically handle data querying, analysis, and visualization. This is ideal for integrating HENGSHI's data analysis capabilities into your own AI chat tools.

html
<div class="conversation"></div>
<script src="https://develop.hengshi.org/assets/hengshi-copilot@<version>.js"></script>
<script>
  // Using prompt configuration, SDK will automatically handle data querying and analysis
  const chat = {
    prompt: "Module distribution of bug issues"
  };
  const container = document.querySelector('.conversation');
  window.Copilot.renderChat(container, chat);
</script>

Static Rendering Mode

This mode is suitable for displaying existing analysis results, where you need to provide a complete chat data structure.

html
<div class="conversation"></div>
<script src="https://develop.hengshi.org/assets/hengshi-copilot@<version>.js"></script>
<script>
  // Provide complete chat data structure for static rendering
  const chat = {
    "meta": "refineQuestion,answer,chart,chartConfig",
    "conversationId": 226,
    "uid": "00747081-9cc3-4c7c-b3de-735244498fd6",
    "prompt": "Module distribution of bug issues",
    "answer": "Bug issues are distributed relatively evenly across modules. The module with the most bugs has 4, while most other modules have 2-3 bugs.",
    "refineQuestion": "Please provide the distribution of bug issues across modules",
    "options": {
      "charts": [],
      "chartsData": []
    },
  };
  const container = document.querySelector('.conversation');
  window.Copilot.renderChat(container, chat);
</script>

Parameter Description

The Copilot.renderChat method accepts two parameters:

  1. container: DOM element used for rendering the chat content

  2. chat: Chat data object, supporting two configuration approaches:

    a. Dynamic Query Configuration:

    • prompt: String, the user's question. When this configuration is provided, the SDK will automatically perform data querying and analysis.
    • meta: (Optional) String, specifies the content combination to render
    • openChartConfig: (Optional) Boolean, controls whether to expand the chart logic panel, defaults to true
    • onCompletionDone: (Optional) Function, callback triggered when completion is done

    b. Static Rendering Configuration:

    • meta: String, specifies the content to render, can be a combination of fields like refineQuestion, answer, chart, and chartConfig
    • conversationId: Conversation ID
    • uid: Unique identifier for the conversation
    • prompt: Original question
    • answer: AI response content
    • refineQuestion: Refined question
    • charts: Array of chart configurations
    • chartsData: Array of chart data

Through this approach, you can:

  1. Integrate HENGSHI's data analysis capabilities into your own AI chat tools
  2. Display specific analysis results in web pages, reports, or dashboards

Integrating Copilot into Dashboards in HENGSHI SENSE

The method of invoking Copilot in the HENGSHI SENSE system is similar to the aforementioned HTML approach.

First, load the Copilot SDK through code in the global JS file:

js
// 1. Keep the copilot SDK synchronized with the system's store data
window.__INITIAL_STATE__ = window._hs_store_.getState();
// 2. Import the copilot SDK code
var script = document.createElement('script');
script.src = 'https://develop.hengshi.org/assets/hengshi-copilot@<version>.js';
script.async = true;
script.onload = function() {
  // 3. Reset the SDK base URL
  window.__hs_sdk_base_url__ = undefined;
  // 4. Create the SDK container
  var copilotRoot = document.createElement('div');
  copilotRoot.id = 'copilot-root';
  copilotRoot.style.width = '100%';
  copilotRoot.style.height = '100%';
  copilotRoot.style.position = 'fixed';
  copilotRoot.style.inset = '0';
  copilotRoot.style.zIndex = '9999';
  copilotRoot.style.pointerEvents = 'none';
  document.body.appendChild(copilotRoot);
  // 5. Assign values to the custom sandbox variable for use in custom JS
  window.myJS = window.myJS || {};
  window.myJS.innerWidth = window.innerWidth;
  window.myJS.innerHeight = window.innerHeight;
  window.myJS.Copilot = Copilot;
  window.myJS.CopilotRoot = copilotRoot;
};
document.body.appendChild(script);

Next, you can use a control button in the dashboard to trigger Copilot. Add a click event in the button control settings to execute the corresponding JS code.

js
if (!myJS) {
  throw new Error('hengshi copilot sdk not loaded yet');
}
if (!myJS.copilotConfig) {
  var stylesheet = `html.hengshi-copilot-sdk, html.hengshi-copilot-sdk body {width:100% !important; height: 100% !important;}
    html.hengshi-copilot-sdk body.hst {background: transparent;}
    .react-draggable {pointer-events: all;}`;
  myJS.copilotConfig = {
    locale: 'zh-CN',
    stylesheet: stylesheet,
    closable: true, // Set the dialog to be closable
    draggable: { // Enable dialog dragging functionality; set to false or omit if not needed
      enable: true,
      minWidth: 440,
      minHeight: 440,
      position: {
        x: myJS.innerWidth - 480,
        y: 20,
      },
      size: {
        width: 440,
        height: myJS.innerHeight * 0.8,
      },
      // Remember the dragged position and size
      onDragStop: onDragStop,
      onResize: onResize,
    },
    // Set the source of dialog data
    userConfig: {
      sourceAppId: params.appId, // params are the parameters provided within the scope of the button control JS events, params.appId is the application id of the dashboard to which the button belongs.
      dataSources: [
        {
          dataAppId: 354, // Data package ID
          datasetId: 26, // Dataset ID
        },
      ],
      dataAppId: 354, // Data package ID
      datasetId: 26, // Dataset ID
    },
  };
}
function onDragStop(event, position) {
  myJS.copilotConfig.draggable.position = position;
}
function onResize(event, position, size) {
  myJS.copilotConfig.draggable.position = position;
  myJS.copilotConfig.draggable.size = size;
}
function launchCopilot() {
  if (typeof myJS.Copilot === 'undefined') {
    (function (fn) {
      fn(launchCopilot);
    })(requestIdleCallback || setTimeout);
  } else {
    if (!myJS.copilot) {
      myJS.copilot = new myJS.Copilot(myJS.CopilotRoot, myJS.copilotConfig);
    } else {
      myJS.copilot.show(); // Use show method to display existing instance
    }
  }
}

if (myJS.copilot) {
  myJS.copilot.hide(); // Use hide method to hide instance instead of destroying
} else {
  launchCopilot();
}

User Manual for Hengshi Analysis Platform