Skip to content

AI Assistant Integration Guide

This document will guide you through a few simple steps to integrate or embed the AI Assistant into your system. HENGSHI SENSE AI Assistant offers multiple integration methods, allowing you to embed the AI Assistant into your system via iframe, JS SDK, or API calls.

1. Integration via iframe

The iframe integration method is the simplest. You only need to add an iframe element in your HTML file (or Vue/React component) and set its src attribute to the URL of the AI Assistant.

html
<iframe
  src="https://develop.hengshi.org/copilot"
  width="100%"
  height="100%">
</iframe>

URL Query String Parameters

We provide some optional URL parameters that you can adjust as needed.

Data Source

You can specify the data source for the AI Assistant conversation in the following ways:

  1. Conversation with the data source of the chart
?appId={App ID}&chartId={Chart ID}
  1. Conversation with the data model of the data package
?dataAppId={Data Package ID}&datasetId={Dataset ID}
Theme Color

You can specify the theme color for the charts within the AI Assistant conversation:

?chartTheme={Dashboard Theme ID}

The Dashboard Theme ID can be found in the theme dropdown menu when editing the dashboard in your application creation area, as shown below:

Dashboard Theme ID

Display Specific Conversations

We also provide conversationId and chatUid parameters, which allow multiple values separated by commas to specify displaying only specific conversations. These two parameters can be used individually or in combination.

?conversationId={Conversation ID1,Conversation ID2}&chatUid={uid1,uid2}
Login Authentication

If you need to integrate in other systems, you may need to perform login authentication, which can be done by passing login information via the jwt parameter:

?activeAuth=jwt-param&jwtParam={JWT Parameter}

2. Integration via JS SDK

The JS SDK integration method requires you to include the AI Assistant's JS file in your HTML file (or Vue/React component) and call the relevant API. This will require you to have some JavaScript development experience.

You can go to the Settings -> Feature Configuration -> AI Assistant -> Console page within the system, click Options -> Copilot SDK to open the integration page guide. In the drawer popup, you will find the SDK link for integration, for example:

https://develop.hengshi.org/assets/hengshi-copilot@5.3.js

Integrate Copilot SDK in Other Systems

We provide example code for integrating Copilot SDK in front-end frameworks such as Vue, React, and pure JS. You can choose according to your needs. This requires you to have some front-end development experience.

js
<!DOCTYPE html>
<html lang="en">
<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@5.3-SNAPSHOT.js" defer></script>
    <style>
        /* Add styles if needed */
    </style>
</head>
<body>
    <button id="trigger-ai">Trigger AI Assistant</button>
    <div id="copilot-root" style="display: none;"></div>

    <script>
        const copilotConfig = {
            locale: 'zh-CN',
            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';
                }
            }
        }

        function destroyCopilot() {
            if (copilotInstance) {
                copilotInstance.destroy();
                copilotInstance = null;
                copilotRoot.style.display = 'none';
                button.textContent = 'Trigger 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('Trigger AI Assistant');

  const copilotConfig = {
    locale: 'zh-CN',
    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@5.3-SNAPSHOT.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');
      }
    }
  };

  const destroyCopilot = () => {
    if (copilotInstance) {
      copilotInstance.destroy();
      setCopilotInstance(null);
      setButtonLabel('Trigger 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('唤起AI助手');

    const copilotConfig = {
      locale: 'zh-CN',
      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 = '关闭AI助手';
        }
      }
    }

    function destroyCopilot() {
      if (copilotInstance.value) {
        copilotInstance.value.destroy();
        copilotInstance.value = null;
        buttonLabel.value = '唤起AI助手';
      }
    }

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

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

<style>
/* 添加样式(如果需要) */
</style>

Authentication

When integrating the SDK, you may need to perform authentication. We provide two methods:

  1. SSO Single Sign-On: If your system is already integrated with HENGSHI SENSE, no additional login is required.
  2. JWT Authentication: Use JWT for login authentication with the following code:
js
fetch('https://develop.hengshi.org/api/auth/login-info?activeAuth=jwt-param&jwtParam=your JWT parameter')
  .then(response => {
    // Login successful, continue using the SDK
  })
  .catch(error => {
    // Login failed, handle the error
  });

Tip

Ensure that you have configured JWT in 'Settings -> Organization Management -> Authentication Method -> JWT Request Parameters'.

Replace 'your JWT parameter' with the actual JWT parameter.

Integrating Copilot in Dashboards within HENGSHI SENSE

Calling Copilot in HENGSHI SENSE is similar to the HTML method described above.

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

js
// 1. Keep copilot sdk in sync with the system's store data
window.__INITIAL_STATE__ = window._hs_store_.getState();
// 2. Import copilot sdk code
var script = document.createElement('script');
script.src = 'https://develop.hengshi.org/assets/hengshi-copilot@5.3-SNAPSHOT.js';
script.async = true;
script.onload = function() {
  // 3. Reset sdk base url
  window.__hs_sdk_base_url__ = undefined;
  // 4. Create 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 to custom sandbox variable for custom js usage
  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);

Then, you can use Control Buttons in the dashboard to invoke Copilot, adding 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 dialog closable
    draggable: { // Set dialog draggable, can be set to false or not set 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 drag position and size
      onDragStop: onDragStop,
      onResize: onResize,
    },
    // Set dialog data source
    userConfig: {
      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 {
    myJS.copilot = new myJS.Copilot(myJS.CopilotRoot, myJS.copilotConfig);
  }
}
if (myJS.copilot) {
  myJS.copilot.destroy();
  myJS.copilot = null;
} else {
  launchCopilot();
}

Copilot Configuration Options

You can adjust the configuration options as needed:

ts
// Copilot configuration definition
interface ICopilotConfig {
  userConfig: {
    appId: number;
    chartId?: number;
    dataAppId: number;
    datasetId?: number;
  };
  closable?: boolean;
  draggable?: boolean | {
    enable: boolean;
    minWidth: number;
    minHeight: number;
    bounds: 'window' | 'parent';
    position: {
      x: number;
      y: number;
    };
    size: {
      width: number;
      height: number;
    };
  };
  locale?: string;
  stylesheet?: string;
  className?: string;
  style?: React.CSSProperties;
  bodyClassName?: string;
  bodyStyle?: React.CSSProperties;
  header?: string | React.ReactElement;
  systemMsg?: string;
  parser?: {
    selectText: string;
    onSelect: (chart: any) => void;
  };
  chartTheme?: string | Theme;
}
interface Theme {
  base?: string | number;
  background?: string;
  color?: string;
  fontSize?: number;
  fontFamily?: any;
  fontWeight?: string;
  textAlign?: string;
  borderColor?: string;
  borderWidth?: number;
  borderStyle?: string;
  borderRadius?: number;
  boxShadow?: string;
  chartBackground?: string;
  chartPadding?: number;
  schema?: string[];
  mapTheme?: string;
}

// Create Copilot configuration object
const copilotConfig: ICopilotConfig = {
  userConfig: {
    // Either appId and datasetId or chartId must be provided
    // Example: appId: 130870, datasetId: 1 // Data package id + Dataset id
    // or
    // appId: 130870, chartId: 1 // Application id + Chart id
  },
  closable: true, // Optional, whether to show the close button, default is false
  draggable: {
    enable: false,
    minWidth: 400,
    minHeight: 400,
    bounds: 'window',
    position: {
      x: window.innerWidth - 480,
      y: 20,
    },
    size: {
      width: 440,
      height: window.innerHeight * 0.8,
    },
  },
  locale: 'zh-CN', // Optional, language setting, default is Simplified Chinese
  stylesheet: '', // Optional, custom CSS styles
  className: '', // Optional, custom class name
  style: {}, // Optional, custom style
  bodyClassName: '', // Optional, dialog content container class name
  bodyStyle: {}, // Optional, dialog content container style
  header: '', // Optional, additional content in the title
  systemMsg: '欢迎使用智能分析助手', // Optional, welcome message, default value
  parser: {
    selectText: '选择图表', // Button text
    onSelect: (chart) => {
      // Event triggered when the button is clicked, chart parameter needs to be defined according to the actual usage scenario
      console.log('Chart selection event', chart);
    },
  },
  chartTheme: 'HAWAII_SEA', // Optional, chart theme, refer to dashboard theme
};

Customizing AI Assistant Styles

If you want to customize the AI assistant's styles, you can achieve this with the following CSS code:

css
/* Example: Batch Customize Skin Styles Using CSS */
*, ::before, ::after {
  --brand: #4CAF50; // This is the overall brand color of the system
}
/* Set the root element style for the AI assistant dialog box */
.hengshi-copilot-sdk .hst-copilot {
  position: fixed;
  // z-index: 1; // Set as needed
  top: 10vh;
  right: 50px;
  width: 440px;
  height: 80vh;
  border-width: 1px;
}
/* Set the style for the AI assistant title element */
.hengshi-copilot-sdk .hst-copilot-header {
  color: #fff;
  background-color: darkslategray;
}
/* Set the style for the AI assistant conversation area element */
.hengshi-copilot-sdk .hst-copilot-conversations {
  border-color: #aaa;
  background-color: darkslategray;
  height: calc(100% - 50px);
}
/* Set the style for the AI assistant message element */
.hengshi-copilot-sdk .hst-copilot-msg,
/* Set the style for the ICON operation element below the AI assistant message */
.hengshi-copilot-sdk .hst-copilot-msg > .hs-relative > *,
/* Set the style for the AI assistant auxiliary information element */
.hengshi-copilot-sdk .hst-copilot-mark {
  color: #fff;
}
.hengshi-copilot-sdk .hst-copilot-mark .hs-bg-\[color\:var\(--hs-bg-lighter\)\] {
  background-color: transparent;
}
/* Set the background style for the ICON operation element when hovered below the AI assistant message */
.hengshi-copilot-sdk .hover\:hs-bg-\[\#eee\]:hover {
  --tw-bg-opacity: .2;
}
/* Set the style for the AI assistant AI message bubble element */
.hengshi-copilot-sdk .hst-copilot-msg-bot .hst-copilot-msg-inner,
/* Set the style for the AI assistant user message bubble element */
.hengshi-copilot-sdk .hst-copilot-msg-user .hst-copilot-msg-inner {
  border-color: darkgray;
  background-color: darkgray;
}
/* Set the background color for the AI assistant recommended question bubble */
.hengshi-copilot-sdk .hst-copilot-msg-inner .hst-copilot-msg-inner {
  background-color: slategray !important;
}
/* Set the style for the AI assistant input content area element */
.hengshi-copilot-sdk .hst-copilot-prompt {
  color: #fff;
  background-color: darkslategray;
}
/* Set the style for the AI assistant input box element */
.hengshi-copilot-sdk .hst-copilot-prompt textarea {
  color: #fff;
  border-color: darkgray !important;
}
.hengshi-copilot-sdk .hst-copilot-prompt .hs-from-\[\#f1f1f1\],
.hengshi-copilot-sdk .hst-copilot-prompt .hs-to-\[\#f1f1f1\] {
  --tw-gradient-to: darkslategray;
}

Pass the above CSS as a string in the stylesheet in the Copilot configuration:

ts
// Create Copilot configuration object
const copilotConfig: ICopilotConfig = {
  ...
  stylesheet: css, 
  ...
};

3. API Integration

API integration also has two methods: frontend and backend. The frontend method directly calls the js API provided by the Copilot SDK, while the backend method uses http interfaces.

JS SDK API

1. Intervene via JS

The Copilot SDK provides a headless mode, which means no UI is needed, only the js API is called, and interaction with the backend is done through http interfaces. This means you can integrate the Copilot SDK into your existing frontend project (such as your own AI assistant) by calling the js API wherever needed.

Pass headless: true in CopilotConfig to enable headless mode. In this case, the Copilot UI will not be rendered on the page, and you can control the behavior of Copilot by calling the js API.

Just like the loading method in UI mode, after new Copilot, the returned copilot instance provides the following Copilot states, data, and functions:

js
{
  api: {...},
  // Copilot state
  isLoading: false,
  // Current conversation 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 conversation list
  onFetchConversations: (offset = 0, limit = 10) => {},
  // Create conversation
  onCreateConversation: prompt => {},
  onFetchConversation: id => {},
  onClearConversations: () => {},
  onFetchPreviousConversations: () => {},
  // Create Q&A
  onCreateChat: body => {},
  onDeleteChat: (id, chatUid) => {},
  onFetchSuggestions: refresh => {},
  onFetchFavorites: () => {},
  onCancelChatFavorite: (id, chatUid) => {},
  // Render chart
  renderChart: (container, { id, chart, chartTheme }) => {},
}

2. Call HTTP API via JS

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

js
// 1. Create conversation
const { body: { data: conversation }} = await copilot.api.requestCreateConversation({
  body: {
    title: 'This is the title of this conversation',
  },
}).value;
/**
conversation = {
  "id": 135,
  "title": "This is the title of this conversation",
  "createdBy": 268,
  "createdAt": "2024-09-14 15:47:23",
  "updatedBy": 268,
  "updatedAt": "2024-09-14 15:47:23"
}
*/
// 2. Create Q&A
const { body: { data: chat }} = await copilot.api.requestCreateChat({
  id: conversation.id,
  body: {
    prompt: 'Which director has the highest box office?', // This is the user's question
    userConfig: { // Refer to the userConfig in the above copilotConfig
      dataAppId: 129150,
      datasetId: 1,
    }
  },
  qs: {
    sync: true, // Whether to execute synchronously
    timeout: 1000 * 60, // Timeout, in milliseconds
  },
}).value;
/**
chat = {
    "conversationId": 135,
    "prompt": "Which director has the highest box office?",
    "answer": "Christopher Nolan has the highest box office.",
    "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 box office?",
    "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_产品表}}.{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_产品表}}.{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
            ]
        }
    ]
}
*/

Backend HTTP API Integration

For backend HTTP API integration, please refer to the API documentation: Complete API Call Example from User Question to Data Acquisition

HENGSHI SENSE Platform User Manual