页面树结构
转至元数据结尾
转至元数据起始

1. 需求背景与适用场景

1.1 需求背景

Smartbi NLA 本身是提供了多种客户端供用户选择的。如果常用PC,我们有基于PC浏览器的AIWeb页面;如果常用手机,我们有基于手机的App或者使用手机浏览器访问的AIChatView页面;我们还提供了基于钉钉的使用页面。

但是,这些都是Smartbi NLA平台内置的使用页面,页面样式和操作方式是基于产品内置好的。如果用户想把自然语言查询集成到用户自己的APP里面,或者集成到用户其他的平台里面,我们内置的页面样式可能不太符合用户的要求,集成起来就不自然。

为了让用户在使用Smartbi自然语言查询的时候,可以自定义前端页面,我们提供了基于Java语言实现的RestFul API接口。本文主要就是介绍如何使用Smartbi NLA的接口实现自己的前端页面。

1.2 适用场景

使用Smartbi NLA 提供的API适合以下场景开发:

  • 觉得Smartbi内置的对话界面不好看,可以使用Smartbi扩展包机制重新开发使用界面
  • 需要将对话嵌入到集团内部APP中,可以定制Android/IOS原生界面,或者嵌入自定义H5页面
  • 需要嵌入到其他的Web平台中,可以定制H5页面
  • 需要嵌入其聊天工具中(如:钉钉、微信等),可以定制符合聊天工具要求的页面
  • 其他使用场景,可以咨询Smartbi客服团队,获取支持

2. 逻辑架构

【用户自定义对话界面-逻辑架构图】

说明:

  • 用户自定义界面,可以脱离Smartbi,嵌入在第三方平台里面,包括:
    • 第三方Web应用
    • 第三方Android应用
    • 第三方IOS应用


如果是Web页面,推荐使用Smartbi扩展包方式开发集成页面。这样可以避免跨域问题。如果需要在第三方应用中开发,需要做对所有API做服务端转发。

3. Smartbi接口调用说明

NLA接口是基于Smartbi标准远程接口实现的,Smartbi的远程接口,Smartbi接口的调用方法如下:


参数名

说明

接口URL

http://server:port/smartbi/vision/RMIServlet

输入参数

className

服务类名


methodName

服务方法名


params

方法参数,以数组形式传递

返回值

返回JSON对象


retCode

返回码:0 - 正确


result

方法返回结果,不同方法返回不一样


duration

执行时间:毫秒

【PostMan - 登录方法测试样例】


4. API说明

Smartbi NLA提供了比较丰富的二次开发API,本次Demo只使用了一部分。这个章节,重点介绍下本次Demo使用的API。

以下介绍的接口,调用方法和调用URL是一样的,区别仅仅是参数不同,所以对每个API不会重复介绍调用方法,具体调用方法请参考:上一章节的 “Smartbi接口调用说明”

4.1 登录Smartbi


URL参数名

URL参数值


输入参数

className

UserService


methodName

clickLogin


params

userName

样例:["demo","demo"]

password

返回值

retCode

0

非0表示错误

result

true

非0时是错误信息

4.2 登录NLA


URL参数名

URL参数值

说明

输入参数

className

AIChatRemoteService


methodName

loginIfSmartbiLogged


params

[]

样例:[]

返回值

retCode

0

非0表示错误

result

{\"token\":\"CE3CE70AC12B70052A507D4B560E5374\"}

NLA 的token信息,这个非常重要,后续NLA所有方法需要用到这个token;

非0时是错误信息

4.3 执行查询


URL参数名

URL参数值

说明

输入参数

className

AIChatRemoteService


methodName

query


params

question

问句

token

loginIfSmartbiLogged方法获取的token

isMultiRound

是否多轮,设置false

datasetId

数据集Id,为空则自动识别数据集

reportId

报表Id,设置为空

uuid

设置为空

userChoseThemeId

交互式问答选择的Id,或者用户强制的模型Id;设置为空

isMobileCall

是否手机请求,设置为false

getPageBO

是否需要仪表盘定义,设置为false

返回值

retCode

0

非0表示错误

result


见下表

  • query方法的返回值比较复杂,下表会详细说明query的返回值。

第一层result的属性

属性含义

说明

code

NLA请求返回码

0表示成功,负数表示错误,整数是路由码

message

错误信息

负数才有值

result

JSON对象

不同的code,返回的内容不一样,详细内容见后续表格

  • query返回 result.code=0,仅列举需要的属性(正确查询

第二层result属性

属性含义

说明

uuid

本次查询uuid

记录查询日志id

rowsCount

本次查询结果数据总行数

表格才生效

currentRows

当前返回数据行数

表格才生效

currentPage

当前页码

表格才生效

rowsPerPage

每页行数

表格才生效

clientId

本次查询id

记录查询id,用于翻页时标记查询缓存

html

返回结果

 如果是图形,则返回ECharts定义

 如果是表格直接返回html

portletType

仪表盘组件类型(queryType=PageQuery才生效)

 ECHARTS_BAR     图形:柱图

 ECHARTS_LINE     图形:线图

 ECHARTS_PIE       图形:饼图

 TABLE_LIST           表格:清单表

 TABLE_CROSS       表格:交叉表

queryType

查询类型

 PageQuery(数据模型、业务主题出图时)

 InsightQuery(业务主题)

 CombinedQuery(业务主题)

question

本次查询的问题


resultTips

本次查询的条件

where或者having条件

themeId

本次查询的数据模型/业务主题id


  • query返回 result.code=50,仅列举需要的属性(选择不确定实体
    • 啊啊啊

第二层result属性

属性含义

说明

intentionType

选择类型

 sim_table        选择不确定的数据集(数据模型、业务主题)

 fuzzy_entity    选择不确定的维度、指标、成员

interActionItems

需要选择的内容

用于构造界面

intentionParams

模型识别参数

需要原样返回给服务器

  • 选择数据集样例(sim_table )

{
    "queryIntention": "sim_table",
    "rhetoricalQuestionType": "sim_table",
    "desc": "发现多个相似表,请从以下列表进行选择并确认。",
    "fuzzyObjects": null,
    "alternatives": [
        [
            "业务主题:主流热销私家车",
            "数据模型:hcy热销车"
        ]
    ],
    "alternativesRealValue": [
        [
            "THEME.demo2019.CSAC汽车销售分析",
            "I8a8aa3ed017bc404c404760a017bc40bc0b90004"
        ]
    ],
    "userSelected": null,
    "otherParams": "{\"system_question\":\"发现多个相似表,请从以下列表进行选择并确认。\",\"id_list\":[[\"THEME.demo2019.CSAC汽车销售分析\",\"I8a8aa3ed017bc404c404760a017bc40bc0b90004\"]],\"fuzzy_entity_list\":[],\"option_list\":[[\"业务主题:主流热销私家车\",\"数据模型:hcy热销车\"]],\"theme_id\":\"\",\"question_ori\":\"去年长安各车型的销售量\"}"
},

【interActionItems--选择表格部分】

【配套页面样子】

  • 选择维度、指标、成员样例(fuzzy_entity)

{
    "queryIntention": "nl2sql",
    "rhetoricalQuestionType": "fuzzy_entity",
    "desc": "发现不确定的实体,请从以下列表进行选择并确认。",
    "fuzzyObjects": [
        "传祺"
    ],
    "alternatives": [
        [
            "成员:【9主流热销私家车/品牌/传祺】",
            "成员:【9主流热销私家车/车型/传祺GA4】"
        ]
    ],
    "alternativesRealValue": [
        [
            {
                "agg": "",
                "type": "dimension_where",
                "operator": "==",
                "path": "9主流热销私家车/品牌",
                "synonym": "",
                "sorted": "",
                "show_content": "成员:【9主流热销私家车/品牌/传祺】=【传祺】",
                "name": "品牌",
                "member": [
                    "传祺"
                ],
                "id": "AUGMENTED_DATASET_FIELD.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column5",
                "keyword": "传祺",
                "identify_type": "",
                "value": "",
                "time_level": ""
            },
            {
                "agg": "",
                "type": "dimension_where",
                "operator": "==",
                "path": "9主流热销私家车/车型",
                "synonym": "",
                "sorted": "",
                "show_content": "成员:【9主流热销私家车/车型/传祺GA4】=【传祺】",
                "name": "车型",
                "member": [
                    "传祺GA4"
                ],
                "id": "AUGMENTED_DATASET_FIELD.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column6",
                "keyword": "传祺",
                "identify_type": "",
                "value": "",
                "time_level": ""
            }
        ]
    ],
    "userSelected": null,
    "otherParams": "{\"entity\":[{\"agg\":\"\",\"id\":\"AUGMENTED_DATASET_LEVEL.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column9-column9_Year-LEVEL-1655372258460\",\"identify_type\":\"auto\",\"keyword\":\"2022年\",\"member\":[\"2022\"],\"name\":\"年\",\"operator\":\"==\",\"path\":\"日期_时间维度/年\",\"sorted\":\"\",\"synonym\":\"\",\"time_level\":\"year\",\"type\":\"dimension_where\",\"value\":\"\"},{\"agg\":\"\",\"id\":\"AUGMENTED_DATASET_FIELD.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column6\",\"identify_type\":\"auto\",\"keyword\":\"车型\",\"member\":[],\"name\":\"车型\",\"operator\":\"\",\"path\":\"9主流热销私家车/车型\",\"sorted\":\"\",\"synonym\":\"\",\"time_level\":\"\",\"type\":\"dimension\",\"value\":\"\"},{\"agg\":\"sum\",\"id\":\"AUGMENTED_DATASET_MEASURE.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column10_1686644374005\",\"identify_type\":\"auto\",\"keyword\":\"销售量\",\"member\":[],\"name\":\"销售量\",\"operator\":\"\",\"path\":\"9主流热销私家车/销售量\",\"sorted\":\"\",\"synonym\":\"\",\"time_level\":\"\",\"type\":\"measure\",\"value\":\"\"}],\"id\":\"I8a8aa3ed017bc404c404760a017bc40bc0b90004\",\"ner_result\":\"[{\\\"label\\\":\\\"Member\\\",\\\"idx\\\":[2,3],\\\"entity\\\":\\\"传祺\\\"},{\\\"label\\\":\\\"Dimension\\\",\\\"idx\\\":[5,6],\\\"entity\\\":\\\"车型\\\"},{\\\"label\\\":\\\"Measure\\\",\\\"idx\\\":[8,10],\\\"entity\\\":\\\"销售量\\\"}]\",\"query_type\":\"nl2sql_query\",\"uncertain_entity\":{\"传祺\":[[\"传祺\",\"品牌\",\"m\"],[\"传祺GA4\",\"车型\",\"m\"]]},\"uncertain_entity_index\":{\"传祺\":[6,7]},\"unrecognized_word\":{},\"word_cut\":[\"2022年\",\"传祺\",\"各\",\"车型\",\"的\",\"销售量\"]}"
}


4.4 选择不确定的实体(维度、指标、成员)

该方法,最主要就是设置 interActionInput.userSelected


URL参数名

URL参数值

说明

输入参数

className

AIChatRemoteService


methodName

rhetoricalQuestionResultInput


params

question

问句

datasetId

数据集Id,query返回的themeId

uuid

query返回的uuid

isMultiQuery

多否多轮查询,设置false

multiMark

多选标记,设置空

interActionInput

在query方法返回的interActionItems属性基础上,添加了userSelected属性内容,填写了用户选择的内容的Id(在interActionItems属性中有提供)

intentionParams

把query返回的结果中的intentionParams属性原样传递回服务器

token

loginIfSmartbiLogged方法获取的token

isMobileCall

是否手机请求,设置为false

getPageBO

是否需要仪表盘定义,设置为false

返回值

retCode

0

参考query方法

result


参考query方法

4.5 选择不确定的模型

该方法,最主要就是设置 datasetId


URL参数名

URL参数值

说明

输入参数

className

AIChatRemoteService


methodName

manualChangeTheme


params

token

loginIfSmartbiLogged方法获取的token

question

问句

datasetId

用户在界面上选择的模型Id

uuid

query返回的uuid

isMobileCall

是否手机请求,设置为false

getPageBO

是否需要仪表盘定义,设置为false

返回值

retCode

0

参考query方法

result


参考query方法


4.6 退出登录


URL参数名

URL参数值

说明

输入参数

className

UserService


methodName

logout


params

[]

样例:[]

返回值

retCode

0

非0表示错误

result



5. Demo开发设计

本次教程,仅以Web开发作为示例,说明如何调用Smartbi NLA的二次开发API实现自定义对话界面的过程。Android应用和IOS应用基本调用API的方法是一致的,区别只是使用的开发平台不同。开发工程师需要在对应的开发平台上做调整。

5.1 界面设计

【Smartbi V11 中的自然语言对话界面设计】

上图是Smartbi V11版本中的自然语言对话界面,主要是基于一个对话式聊天的方式实现的自然语言查询过程。期望用户在与“AI模型”在对话的过程中获取到用户期望的分析数据。并可以将分析结果插入到仪表盘中,实现分析结果的固化和分享。

本次教程,我们没有必要做这么复杂的设计,我们以一个简单的设计,来说明Smartbi NLA 二次开发的API是如何使用的。

【Demo开发样例-界面设计】

5.2 组件设计

在界面设计中,我们需要将界面按照功能区域,拆分成不同的组件。

【界面组件拆分】

  • 组件设计
    • 标题栏与输入框比较简单,不需要单独做展示组件
    • 查询条件组件
    • 结果区组件:
      • 表格显示组件(交叉表、清单表)
      • 图形显示组件(柱图、线图、饼图、散点图)
      • 交互式问答组件(选择模型、选择实体)

5.3 NLA接口调用流程

graph TD
      A[自然语言查询] -->|login| B(登录Smartbi)
      B -->|loginIfSmartbiLogged| C(登录NLA)
      C -->|query| D{执行查询}
      D -->|无歧义| E[展示查询结果]
      D -->|不确定的模型| F[选择模型]
      D -->|不确定的实体| G[选择实体]
      F --> |manualChangeTheme|D
      G --> |rhetoricalQuestionResultInput|D

【NLA接入流程】

如果是在Smartbi扩展包内实现NLA调用,可以省略登录Smartbi的过程。

6. Demo代码编写

6.1 demo使用开发框架说明

本次demo,为了更好的说明第三方集成情况。采用完全模拟第三方平台的开发模式。demo应用使用的开发框架如下:

  • Java框架 -- Springboot 2
  • 前端框架 -- Vue2 、elementui、 jquery、Echarts
  • SmartbiV11 -- 非常重要,本次开发对接的API是基于V11版本提供的

6.2 代码结构说明

【代码结构】

@startuml
  前端HTML -> JS_API: 调用前端接口
  JS_API --> APIController: 调用服务端接口
  
  APIController --> SmartbiAPI: 调用Java远程接口
  SmartbiAPI --> Smartbi_Server: 调用Smartbi接口
  @enduml
  

【接口调用时序】

6.2.1 主要代码文件说明


文件

说明

后端Java代码

SmartbiAPI.java

Smartbi api 接口

APIController.java

演示程序,提供给前端的接口,实现内容与SmartbiAPI.java对应

前端JS

api.js

前端API接口,与APIController.java 对应

components/result-view.js

查询结果展示路由组件

components/query-tip.js

查询条件展示组件

components/select-entity.js

选择实体展示组件

components/result/chart-view.js

图形显示组件

components/result/table-view.js

表格显示组件

components/result/text-view.js

文字显示组件

components/result/select-view.js

模糊实体选择组件

前端HTML

index.html

页面文件


6.3 登录

【登录部分】

<el-form :inline="true" :model="form1" class="demo-form-inline">
    <el-form-item label="账号:">
        <el-input v-model="form1.user" placeholder="账号"></el-input>
    </el-form-item>
    <el-form-item label="密码:">
        <el-input v-model="form1.password" placeholder="密码" show-password></el-input>
    </el-form-item>
    <el-form-item label="Smartbi地址:">
        <el-input v-model="form1.server" placeholder="Smartbi地址" style="width:350px"></el-input>
    </el-form-item>
    <el-form-item>
        <el-button type="primary" @click="login" :disabled="loginOk">登录</el-button>
    </el-form-item>
</el-form>


login: function(server, userName, password, callback) {
    $.ajax({
        type: "POST",
        url: 'smartbi/login',
        data: {server: server, userName: userName, password: password},
        success: function (data) {
            callback(data);
        },
        error: function (data) {
            alert(data.responseJSON.message);
        }
    });
},

/**
 * 登录Smartbi 和 nla
 * @param server
 * @param userName
 * @param password
 * @return
 */
@RequestMapping("login")
public boolean login(String server, String userName, String password, HttpSession session) throws RemoteException {
    SmartbiAPI smartbi = new SmartbiAPI(server);
    try {
        boolean rs = smartbi.login(userName, password);
        if (rs) {
            rs = smartbi.loginNLA();
            if (rs) {
                saveSmartbi(session, smartbi);
                return true;
            }
        }
    } catch (RemoteException e) {
        logger.error(e.getMessage(), e);
        throw e;
    }
    throw new RemoteException("登录失败");
}

/**
 * 登录
 *
 * @param userName
 * @param password
 * @return 返回 Smartbi token
 */
public boolean login(String userName, String password) throws RemoteException {
    List<Object> list = new ArrayList<>();
    list.add(userName);
    list.add(password);
    String rs = remoteInvokeEx("UserService", "clickLogin", list, true);
    return ("true".equals(rs));
}

/**
 * 登录 NLA
 *
 * @return 返回 nla token
 */
public boolean loginNLA() throws RemoteException {
    List<Object> list = new ArrayList<>();
    String rs = remoteInvokeEx("AIChatRemoteService", "loginIfSmartbiLogged", list);
    JSONObject json = JSON.parseObject(rs);
    if (json.containsKey("token")) {
        this.nlaToken = json.getString("token");
        return StringUtils.isNotBlank(nlaToken);
    } else {
        return false;
    }
}

6.4 执行查询

6.4.1 执行查询

...
<result-view ref="resultView" v-if="status==4"></result-view>
...
query() {
    this.status = 3;
    API.query(this.input1, (data) => {
        if (data) {
            this.status = 4;
            this.$nextTick(() => {
                this.$refs.resultView.setResult(data);
            })
        }
    });
}

/**
 * 查询
 */
query: function(question, callback) {
    $.ajax({
        type: "POST",
        url: 'smartbi/query',
        data: {question: question, datasetId: ""},
        success: function (data) {
            callback(data);
        },
        error: function (data) {
            alert(data.responseJSON.message);
        }
    });
},

@RequestMapping("query")
public String query(String question, String datasetId, HttpSession session) throws RemoteException {
    SmartbiAPI smartbi = getSmartbi(session);
    if (smartbi == null) {
        throw new RemoteException("用户未登录!");
    } else {
        try {
            return smartbi.query(question, datasetId);
        } catch (RemoteException e) {
            logger.error(e.getMessage(), e);
            throw e;
        }
    }
}

/**
 * 执行自然语言查询
 *
 * @param question  问题
 * @param datasetId 模型Id
 * @return
 */
public String query(String question, String datasetId) throws RemoteException {
    List<Object> params = new ArrayList<>();
    params.add(question);
    params.add(this.nlaToken);
    //isMultiRound
    params.add(false);
    params.add(datasetId);
    //reportId
    params.add("");
    //uuid
    params.add("");
    //userChoseThemeId -- 交互式问答选择的Id,或者用户强制的模型Id
    params.add("");
    //isMobileCall
    params.add(false);
    //getPageBO -- 集成仪表盘使用,是否返回仪表盘定义
    params.add(false);
    return remoteInvokeEx("AIChatRemoteService", "query", params);
}

6.4.2 结果展示路由

执行 query 方法后,将返回结果设置给 result-view 组件

setResult(result) {
    this.result = JSON.parse(result);
    this.routerResult();
    this.show = true;
},
/**
 * 路由,显示结果展示组件
 */
routerResult() {
    if (this.result.code == 0) {
        // 正常展示结果
        if (this.isChartsResult()) {
            this.resultComponent = Vue.component('chart-view');
            this.resultContent = this.result.result;
        } else if (this.isTableResult()) {
            this.resultComponent = Vue.component('table-view');
            this.resultContent = this.result.result.html;
        } else {
            this.resultComponent = Vue.component('text-view');
            this.resultContent = "异常展示组件 -- 还没有实现!";
        }
    } else if (this.result.code == 50) {
        // 反问,选择实体
        this.resultComponent = Vue.component('select-view');
        this.resultContent = this.result;
    } else if (this.result.code == 10) {
        // 元数据
        this.resultComponent = Vue.component('text-view');
        this.resultContent = "此处是元数据查询,还没有实现!";
    } else if (this.result.code == 20) {
        // 闲聊
        this.resultComponent = Vue.component('text-view');
        this.resultContent = "此处是聊天内容,还没有实现!";
    } else if (this.result.code == 30) {
        // 打开报表
        this.resultComponent = Vue.component('text-view');
        this.resultContent = "此处是打开报表,还没有实现!";
    } else if (this.result.code == 100) {
        // 空结果
        this.resultComponent = Vue.component('text-view');
        this.resultContent = "查询无结果!";
    } else if (this.result.code == -10) {
        // 如果nl2sql有输出,但smartbi查询数据报错
        this.resultComponent = Vue.component('text-view');
        this.resultContent = "查询报错了!" + this.result.message;
    }
},


6.5 结果展示

6.5.1 表格展示

Vue.component('table-view', {
    name: 'TableView',
    template: '\
<div class="container">\
    <div class="scrollbar" v-html="resultContent" style="max-height: 330px;"></div>\
</div>\
    ',
    props: ["resultContent"]
});

6.5.2 图形展示

var tooltipValueFormatter = function(value) {

    let valueStr = String(value);
    let isFloat = valueStr.indexOf(".") + 1 > 0;
    if (isFloat && typeof(value) == "number") {
        return value.toFixed(2);
    } else if (isFloat && typeof(value) == "string") {
        return Number(value).toFixed(2);
    }
    return value;
}

Vue.component('chart-view', {
    name: 'ChartView',
    template: '\
<div class="container">\
    <div style="height: 270px; width: 100%; background-color: white;" ref="echartContainerId"></div>\
</div>\
    ',
    props: ["resultContent"],
    data() {
        return {
            myChart: null,
            chartType: null,
            option: null,
            onceShowMaxItem: 7
        }
    },
    watch: {
        //queryResult
        resultContent: {
            handler: function (newValue, oldValue) {
                let that = this;
                setTimeout(function () {
                    that.showResult(that.resultContent.portletType, that.resultContent.html);
                }, 100);
            },
            immediate: true
        }
    },
    computed: {
        isEmptyData() {
            //option不存在不是空数据
            if (!this.option) {
                return false;
            }
            let series = this.option.series;
            return !series || (series && series.length === 0) || (!series[0].data || (series[0].data && series[0].data.length === 0))
        }
    },
    methods: {
        showResult(chartType, echartsOption) {
            this.chartType = chartType;
            this.option = JSON.parse(echartsOption);
            if (this.isEmptyData) {
                this.$emit("showEmptyQueryResultView");
                return;
            }
            //该方法可以不需要,主要是对后端返回的option进行加工
            this.processOption();
            this.$nextTick(() => {
                this.myChart = echarts.init(this.$refs.echartContainerId);
                this.myChart.setOption(this.option);
            });
        },
        processOption() {
            let grid = {
                left: '2%',
                right: '2%',
                bottom: '2%',
                top: '7%',
                containLabel: true,
                show: 'true',
                borderWidth: '0'
            };

            if (this.chartType === "ECHARTS_BAR" || this.chartType === "ECHARTS_LINE" || this.chartType === "ECHARTS_COMBINATION__DUAL") {
                let onceShowMaxItem = this.onceShowMaxItem; //一次最多显示多少根柱子,后面运算时用到, 3-7有效,太多没试过,小于3时不太准确
                let dataNum = this.option.xAxis.data.length;
                let processedEnd = 100;
                if (dataNum > onceShowMaxItem) {
                    processedEnd = 100 * onceShowMaxItem / dataNum;
                }
                let dataZoom = [
                    {
                        // start, end是控制echarts图形的缩放比例,%,通过计算end, 控制最多显示多少根柱子(多少单位的X轴长度)
                        type: 'inside',
                        xAxisIndex: [0],
                        start: 0, end: processedEnd,
                        //end: 100  / this.option.xAxis.data.length * 6,
                        zoomOnMouseWheel: false,
                        zoomLock: true,
                    },
                ];

                //设置图形可以左右滚动
                this.option["dataZoom"] = dataZoom;
                //给第一个柱子或线设置显示数值,所有柱子都设置会很挤
                //this.option["series"][0]["label"] = label;
                //解决Y轴刻度被遮挡
                this.option["grid"] = grid;
            }

            if (this.chartType === "ECHARTS_BAR__HORIZONTAL") {
                //解决Y轴刻度被遮挡
                this.option["grid"] = grid;
            }

            if (this.chartType === "ECHARTS_PIE") {
                let label = {
                    normal: {
                        position: "inside"//此处将展示的文字在内部展示
                    }
                };
                //解决饼图标签显示不全
                this.option["series"][0]["label"] = label;
            }
            if (!!this.option.tooltip) {
                this.option.tooltip["confine"] = true;
                this.option.tooltip["valueFormatter"] = tooltipValueFormatter;
            } else {
                let tooltip = {
                    confine: true,
                    valueFormatter: tooltipValueFormatter
                };
                this.option["tooltip"] = tooltip;
            }
        }
    },
    destroyed() {
        if (!!this.myChart) {
            this.myChart.dispose();
        }
        this.myChart = null;
    }
});

6.6 交互式问答

交互式问答的内容,主要是在query方法返回code=50的时候会出现。交互式问答主要有2种情况:

  • 选择模糊的数据集(数据模型、业务主题)
  • 选择模糊的维度、指标、成员

交互式问答,返回的模糊内容,主要在query方法返回的result.interActionItems属性中。详细可以参考“query接口”

6.6.1 选择模糊数据集

【query方法返回result.interActionItems】

<div v-else-if="interactionItem.rhetoricalQuestionType == 'sim_table'">
    <div class="bot-answer-message" ref="chatRecord">{{ interactionItem.desc }}</div>
        <el-radio-group v-model="userSelected[0]" style="margin-top: 10px">
            <div v-for="(item, index) in alternativesItems" :key="index">
                <el-radio :label="index">{{ item }}</el-radio>
            </div>
        </el-radio-group>
    <div>
        <el-button class="btn" @click="handleClickConfirmSimTable" :disabled="userSelected.length == 0">确定</el-button>
    </div>
</div>

【选择模糊数据集界面代码】


【选择模糊数据集界面效果】

doChooseSimTable(userChoseThemeId) {
    API
        .manualChangeTheme(
            this.result.result.question,
            userChoseThemeId,
            this.result.result.uuid,
            (ret) => {
                this.setResult(ret);
            }
    );
}

【选定数据集接口】

manualChangeTheme接口的详细定义,请参考对应API部分说明。

6.6.2 选择模糊维度、指标、成员

【query方法返回result.interActionItems】

<div v-if="interactionItem.rhetoricalQuestionType == 'fuzzy_entity'">
    <select-entity
        :desc="interactionItem.desc"
        :fuzzyEntityName="interactionItem.fuzzyObjects[0]"
        :alternativeList="interactionItem.alternatives[0]"
        @userSelectFuzzyEntity="handleUserSelectFuzzyEntity"></select-entity>
</div>

<div>
    <div style="color: #3370FF; font-size: 14px">【{{ fuzzyEntityName }}】</div>
    <el-checkbox-group v-model="userSelected" style="margin-top: 10px">
        <div v-for="(item, index) in alternativesItems" :key="index">
            <el-checkbox :label="item">{{ item }}</el-checkbox>
        </div>
    </el-checkbox-group>
    ...
</div>

【选择模糊维度、指标、成员界面代码】


【选择模糊维度、指标、成员界面效果】

/**
 * 交互式问答:选择维度、指标或者成员
 * @param interactionItem
 */
doRhetoricalQuestionResultInput(interactionItem) {
    var intentionParams = this.result.result.intentionParams? this.result.result.intentionParams: "";
    API
        .rhetoricalQuestionResultInput(
            this.result.result.question,
            this.result.result.themeId,
            this.result.result.uuid,
            JSON.stringify(interactionItem),
            JSON.stringify(intentionParams),
            (ret) => {
                this.setResult(ret);
            }
        );
},

【选定维度、指标、成员接口】


rhetoricalQuestionResultInput接口的详细定义,请参考对应API部分说明。

{
    "queryIntention": "nl2sql",
    "rhetoricalQuestionType": "fuzzy_entity",
    "desc": "发现不确定的实体,请从以下列表进行选择并确认。",
    "fuzzyObjects": [
        "传祺"
    ],
    "alternatives": [
        [
            "成员:【9主流热销私家车/品牌/传祺】",
            "成员:【9主流热销私家车/车型/传祺GA4】"
        ]
    ],
    "alternativesRealValue": [
        [
            {
                "agg": "",
                "type": "dimension_where",
                "operator": "==",
                "path": "9主流热销私家车/品牌",
                "synonym": "",
                "sorted": "",
                "show_content": "成员:【9主流热销私家车/品牌/传祺】=【传祺】",
                "name": "品牌",
                "member": [
                    "传祺"
                ],
                "id": "AUGMENTED_DATASET_FIELD.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column5",
                "keyword": "传祺",
                "identify_type": "select_fuzzy_entity",
                "value": "",
                "time_level": ""
            },
            {
                "agg": "",
                "type": "dimension_where",
                "operator": "==",
                "path": "9主流热销私家车/车型",
                "synonym": "",
                "sorted": "",
                "show_content": "成员:【9主流热销私家车/车型/传祺GA4】=【传祺】",
                "name": "车型",
                "member": [
                    "传祺GA4"
                ],
                "id": "AUGMENTED_DATASET_FIELD.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column6",
                "keyword": "传祺",
                "identify_type": "",
                "value": "",
                "time_level": ""
            }
        ]
    ],
    //来源alternativesRealValue中选定的内容
    "userSelected": [
        [
            {
                "agg": "",
                "type": "dimension_where",
                "operator": "==",
                "path": "9主流热销私家车/品牌",
                "synonym": "",
                "sorted": "",
                "show_content": "成员:【9主流热销私家车/品牌/传祺】=【传祺】",
                "name": "品牌",
                "member": [
                    "传祺"
                ],
                "id": "AUGMENTED_DATASET_FIELD.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column5",
                "keyword": "传祺",
                "identify_type": "select_fuzzy_entity",
                "value": "",
                "time_level": ""
            }
        ]
    ],
    "otherParams": "{\"entity\":[{\"agg\":\"\",\"id\":\"AUGMENTED_DATASET_LEVEL.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column9-column9_Year-LEVEL-1655372258460\",\"identify_type\":\"auto\",\"keyword\":\"2022年\",\"member\":[\"2022\"],\"name\":\"年\",\"operator\":\"==\",\"path\":\"日期_时间维度/年\",\"sorted\":\"\",\"synonym\":\"\",\"time_level\":\"year\",\"type\":\"dimension_where\",\"value\":\"\"},{\"agg\":\"\",\"id\":\"AUGMENTED_DATASET_FIELD.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column6\",\"identify_type\":\"auto\",\"keyword\":\"车型\",\"member\":[],\"name\":\"车型\",\"operator\":\"\",\"path\":\"9主流热销私家车/车型\",\"sorted\":\"\",\"synonym\":\"\",\"time_level\":\"\",\"type\":\"dimension\",\"value\":\"\"},{\"agg\":\"sum\",\"id\":\"AUGMENTED_DATASET_MEASURE.I8a8aa3ed017bc404c404760a017bc40bc0b90004.Field-demo2019-null-null-car_selling_fact-column10_1686644374005\",\"identify_type\":\"auto\",\"keyword\":\"销售量\",\"member\":[],\"name\":\"销售量\",\"operator\":\"\",\"path\":\"9主流热销私家车/销售量\",\"sorted\":\"\",\"synonym\":\"\",\"time_level\":\"\",\"type\":\"measure\",\"value\":\"\"}],\"id\":\"I8a8aa3ed017bc404c404760a017bc40bc0b90004\",\"ner_result\":\"[{\\\"label\\\":\\\"Member\\\",\\\"idx\\\":[2,3],\\\"entity\\\":\\\"传祺\\\"},{\\\"label\\\":\\\"Dimension\\\",\\\"idx\\\":[5,6],\\\"entity\\\":\\\"车型\\\"},{\\\"label\\\":\\\"Measure\\\",\\\"idx\\\":[8,10],\\\"entity\\\":\\\"销售量\\\"}]\",\"query_type\":\"nl2sql_query\",\"uncertain_entity\":{\"传祺\":[[\"传祺\",\"品牌\",\"m\"],[\"传祺GA4\",\"车型\",\"m\"]]},\"uncertain_entity_index\":{\"传祺\":[6,7]},\"unrecognized_word\":{},\"word_cut\":[\"2022年\",\"传祺\",\"各\",\"车型\",\"的\",\"销售量\"]}"
}

  • rhetoricalQuestionResultInput接口中的 interactionItem 属性,主要是复制 query接口返回的 interActionItems 属性内容,其中需要补充设置 userSelected 属性。
  • userSelected的内容来源于 query 接口返回的 alternativesRealValue 属性,只设置用户选定的序号内容即可。


  • 无标签