1. 需求背景与适用场景

1.1 需求背景

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

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

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

1.2 适用场景

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

2. 逻辑架构

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

说明:


如果是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


见下表

第一层result的属性

属性含义

说明

code

NLA请求返回码

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

message

错误信息

负数才有值

result

JSON对象

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

第二层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


第二层result属性

属性含义

说明

intentionType

选择类型

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

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

interActionItems

需要选择的内容

用于构造界面

intentionParams

模型识别参数

需要原样返回给服务器

{
    "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--选择表格部分】

【配套页面样子】

{
    "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应用使用的开发框架如下:

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年\",\"传祺\",\"各\",\"车型\",\"的\",\"销售量\"]}"
}