Skip to content

通过原生JS 集成 SDK

原生 js sdk 提供的 UI、功能与 iframe 集成类似,直接使用衡石 ChatBI 的对话组件、样式和功能,只需要在你的 HTML 文件中(或 Vue/React 组件中)引入 AI 助手的 JS 文件,并调用相关 API。这将需要您拥有一定的 JavaScript 开发经验。

快速上手

  1. 获取 js sdk 链接

进入衡石系统内设置->功能配置->智能查数助手->控制台页面,点击选项->使用 SDK 嵌入打开集成页面指南,在抽屉弹窗中,您将找到用于集成的 SDK 链接,例如

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

注意

@<version> 是 SDK 的版本号,请确保您使用的 SDK 版本与您的系统版本一致。系统升级时也需要更新。

  1. 引入 js sdk

我们提供了在 vue、react、纯 js 等前端框架中集成 SDK 的示例代码,您可以根据需要进行选择,这要求您需要有一定的前端开发经验。

js
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI助手</title>
    <script type="text/javascript" src="https://develop.hengshi.org/assets/hengshi-copilot@<version>.js" defer></script>
</head>
<body>
    <!-- 在你的项目中合适的位置增加按钮 -->
    <button id="trigger-ai">唤起AI助手</button>
    <!-- 在你的项目中合适的位置增加 sdk 所需容器 -->
    <div id="copilot-root" style="display: none;"></div>

    <script>
        const copilotConfig = { // 定义 AI 助手配置
            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 = '关闭AI助手';
                } else {
                    copilotInstance.show(); // 使用 show 方法显示已存在的实例
                }
            }
        }

        function toggleCopilot() {
            if (copilotInstance) {
                const isVisible = copilotRoot.style.display !== 'none';
                if (isVisible) {
                    copilotInstance.hide(); // 使用 hide 方法隐藏实例,而不是销毁
                    button.textContent = '唤起AI助手';
                } else {
                    copilotInstance.show();
                    button.textContent = '关闭AI助手';
                }
            } else {
                launchCopilot();
            }
        }
    </script>
</body>
</html>
jsx
import React, { useState, useEffect } from 'react';

const CopilotComponent = () => {
  const [copilotInstance, setCopilotInstance] = useState(null);
  const [buttonLabel, setButtonLabel] = useState('唤起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,
    },
  };

  useEffect(() => {
    // 加载 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); // 清理加载的脚本
    };
  }, []);

  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('关闭AI助手');
      } else {
        copilotInstance.show(); // 使用 show 方法显示已存在的实例
        setButtonLabel('关闭AI助手');
      }
    }
  };

  const toggleCopilot = () => {
    if (copilotInstance) {
      const isVisible = document.getElementById('copilot-root').style.display !== 'none';
      if (isVisible) {
        copilotInstance.hide(); // 使用 hide 方法隐藏实例,而不是销毁
        setButtonLabel('唤起AI助手');
      } else {
        copilotInstance.show();
        setButtonLabel('关闭AI助手');
      }
    } 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助手';
        } else {
          copilotInstance.value.show(); // 使用 show 方法显示已存在的实例
          buttonLabel.value = '关闭AI助手';
        }
      }
    }

    function toggleCopilot() {
      if (copilotInstance.value) {
        const isVisible = document.getElementById('copilot-root').style.display !== 'none';
        if (isVisible) {
          copilotInstance.value.hide(); // 使用 hide 方法隐藏实例,而不是销毁
          buttonLabel.value = '唤起AI助手';
        } else {
          copilotInstance.value.show();
          buttonLabel.value = '关闭AI助手';
        }
      } else {
        launchCopilot();
      }
    }

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

<style>
/* 添加样式(如果需要) */
</style>
  1. 启动/打开你的项目,检查是否成功集成。

JS SDK 实例

new Copilot(...) 的结果是一个 copilot 实例,你可以通过这个实例来操作 Copilot。

js
{
  copilotConfig: {
    config: {...}, // 大模型配置信息
    loading: false, // 是否正在加载
    onVerifyConfig: () => {}, // 验证配置信息
    onChangeProvider: () => {}, // 切换模型提供商
    // ...
  },
  context: {
    api: {...},
    endpoints: {...},
    // Copilot 状态
    isLoading: false,
    // 当前会话列表,数据格式参照
    // https://api.hengshi.com/conversation.html#conversation
    conversations: [],
    setConversations: () => {},
    currentConversationId: null,
    setCurrentConversationId: () => {},
    runningChatUid: null,
    setRunningChatUid: () => {},
    setChat: (id, chatUid, chat) => {},
    // 获取历史对话列表
    onFetchConversations: (offset = 0, limit = 10) => {},
    // 创建对话
    onCreateConversation: prompt => {},
    onFetchConversation: id => {},
    onClearConversations: () => {},
    onFetchPreviousConversations: () => {},
    // 创建问答
    onCreateChat: body => {},
    onDeleteChat: (id, chatUid) => {},
    onFetchSuggestions: refresh => {},
    onFetchFavorites: () => {},
    onCancelChatFavorite: (id, chatUid) => {},
  },
  // 控制对话框显示/隐藏
  show: () => {}, // 显示对话框
  hide: () => {}, // 隐藏对话框
}

1. 控制对话框的显示与隐藏

Copilot 实例提供了 showhide 方法来控制对话框的显示与隐藏,这比完全销毁和重新创建实例更加高效:

js
// 创建一个 Copilot 实例
const copilotInstance = new Copilot(copilotRoot, copilotConfig);

// 隐藏对话框(不会销毁实例,只是隐藏界面)
copilotInstance.hide();

// 显示对话框
copilotInstance.show();

// 切换显示/隐藏状态
function toggleCopilot() {
  if (copilotInstance) {
    // 可以通过检查 DOM 元素状态来判断当前显示状态
    const isVisible = copilotRoot.style.display !== 'none';
    if (isVisible) {
      copilotInstance.hide();
      button.textContent = '唤起AI助手';
    } else {
      copilotInstance.show();
      button.textContent = '关闭AI助手';
    }
  }
}

与完全销毁和重新创建实例相比,使用 show/hide 方法有以下优点:

  • 保持对话上下文和状态
  • 更快的响应速度(无需重新初始化)
  • 更少的资源消耗

2. 通过 JS 调用 HTTP API

在上述 copilot 实例中,api 对象中提供了所有 HTTP API 的调用方法,你可以通过调用这些方法来与后端进行交互,如:

2.1 创建对话

js
const { body: { data: conversation }} = await copilot.api.requestCreateConversation({
  body: {
    title: '这里是本次对话的标题',
  },
}).value;
数据示例
js
conversation = {
  "id": 135,
  "title": "这里是本次对话的标题",
  "createdBy": 268,
  "createdAt": "2024-09-14 15:47:23",
  "updatedBy": 268,
  "updatedAt": "2024-09-14 15:47:23"
}

2.2 创建问答

js
const { body: { data: chat }} = await copilot.api.requestCreateChat({
  id: conversation.id,
  body: {
    prompt: '哪个导演拍的电影票房最高', // 这里是用户的问题
    userConfig: { // 参照上述 copilotConfig 中的 userConfig
      dataAppId: 129150,
      datasetId: 1,
    }
  },
  qs: {
    sync: true, // 是否同步执行
    timeout: 1000 * 60, // 超时时间,单位为毫秒
  },
}).value;
数据示例
js
chat = {
    "conversationId": 135,
    "prompt": "哪个导演拍的电影票房最高",
    "answer": "克里斯托弗·诺兰导演拍的电影票房最高。",
    "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": "哪个导演拍的电影票房最高",
    "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
            ]
        }
    ]
}

在衡石系统内的仪表盘里集成 Copilot

在衡石系统中调用 Copilot 方式与上述 HTML 方式是类似的。

首先,在全局 JS 文件中通过代码加载 Copilot SDK:

js
// 1. 保持 copilot sdk 与系统的 store 数据同步
window.__INITIAL_STATE__ = window._hs_store_.getState();
// 2. 引入 copilot sdk 代码
var script = document.createElement('script');
script.src = 'https://develop.hengshi.org/assets/hengshi-copilot@<version>.js';
script.async = true;
script.onload = function() {
  // 3. 重置 sdk base url
  window.__hs_sdk_base_url__ = undefined;
  // 4. 创建 sdk 容器
  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. 赋值给自定义 sandbox 变量以便自定义 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);

然后,您可以在仪表盘中使用控件按钮来唤起 Copilot,在按钮控件的设置中添加点击事件,执行相应的 JS 代码。

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, // 设置对话框可关闭
    draggable: { // 设置对话框拖动功能,不需要拖动功能可以设置为 false 或不设置
      enable: true,
      minWidth: 440,
      minHeight: 440,
      position: {
        x: myJS.innerWidth - 480,
        y: 20,
      },
      size: {
        width: 440,
        height: myJS.innerHeight * 0.8,
      },
      // 设定记住拖动的位置和大小
      onDragStop: onDragStop,
      onResize: onResize,
    },
    // 设置对话数据来源
    userConfig: {
      dataAppId: 354, // 数据包 id
      datasetId: 26, // 数据集 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(); // 使用 show 方法显示已存在的实例
    }
  }
}

if (myJS.copilot) {
  myJS.copilot.hide(); // 使用 hide 方法隐藏实例,而不是销毁
} else {
  launchCopilot();
}

使用 Copilot 嵌入单独的对话框组件

除了嵌入完整的 AI 助手对话功能外,SDK 还提供了一个静态方法 renderChat,允许您将单个对话嵌入到页面的任何位置,非常适合展示特定的 AI 分析结果。

基础用法

html
<div class="conversation"></div>
<script src="https://develop.hengshi.org/assets/hengshi-copilot@<version>.js"></script>
<script>
  // 通过内部 API 获取对话框数据结构
  const chat = {
    "meta": "refineQuestion,answer,chart,chartConfig",
    "conversationId": 226,
    "uid": "00747081-9cc3-4c7c-b3de-735244498fd6",
    "prompt": "bug issue的模块分布情况",
    "answer": "bug issue在各模块中的分布较均匀,最多的模块有4个bug,其余大部分模块有2-3个bug。",
    "refineQuestion": "请提供bug issue在各模块中的分布情况",
    "options": {
      "charts": [],
      "chartsData": []
    },
  };
  const container = document.querySelector('.conversation');
  window.Copilot.renderChat(container, chat);
</script>

参数说明

Copilot.renderChat 方法接受两个参数:

  1. container: DOM 元素,用于渲染对话内容的容器
  2. chat: 对话数据对象,包含以下字段:
    • meta: 字符串,指定要渲染的内容,可以是 refineQuestionanswerchartchartConfig 等字段的组合。这个字段决定了渲染哪些内容。
      • refineQuestion: 精炼后的问题
      • answer: AI 的回答
      • chart: 图表
      • chartConfig: 取数逻辑
    • conversationId: 对话 ID
    • uid: 对话的唯一标识符
    • prompt: 原始问题
    • answer: AI 回答内容
    • refineQuestion: 精炼后的问题
    • charts: 图表配置数组
    • chartsData: 图表数据数组

这种方式可以让您在网页、报表或仪表盘中嵌入特定的 AI 分析结果,而不需要提供完整的聊天界面.

衡石分析平台使用手册