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
- 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.
- 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.
<!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>
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;
<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>
- 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.
{
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:
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:
- Data Package and Dataset Mode:
dataSources
: Array containing multiple data source configuration objectsdataAppId
: Data package IDdatasetId
: Dataset ID
Example:
userConfig: {
dataSources: [
{
dataAppId: 130870,
datasetId: 1,
},
{
dataAppId: 130871,
datasetId: 2,
}
]
}
- Business Indicator Subject Domain Mode:
dataSources
: Array containing multiple subject domain configuration objectssubjectId
: Subject domain ID
Example:
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 titlecopilot.question-answer
: Data performance section titlecopilot.question-reasoning-logic
: Data logic section title
Example:
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
<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 likerefineQuestion
,answer
,chart
, andchartConfig
openChartConfig
: Boolean, controls whether to expand the chart logic panel, defaults totrue
onCompletionDone
: Function, callback triggered when AI completion is done, receives two parameters:conversationId
: Current conversation IDuid
: 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:
// 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
const { body: { data: conversation }} = await copilot.api.requestCreateConversation({
body: {
title: 'This is the title of the conversation',
},
}).value;
Data Structure
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
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
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.
<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.
<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:
container
: DOM element used for rendering the chat contentchat
: 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 renderopenChartConfig
: (Optional) Boolean, controls whether to expand the chart logic panel, defaults totrue
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 likerefineQuestion
,answer
,chart
, andchartConfig
conversationId
: Conversation IDuid
: Unique identifier for the conversationprompt
: Original questionanswer
: AI response contentrefineQuestion
: Refined questioncharts
: Array of chart configurationschartsData
: Array of chart data
Through this approach, you can:
- Integrate HENGSHI's data analysis capabilities into your own AI chat tools
- 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:
// 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.
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();
}