9 Commits

Author SHA1 Message Date
QJ_wonder
8d34dcd5dd Merge fa65b6eff7 into 06fb6ba744 2025-06-09 10:09:35 +08:00
FredTsang
06fb6ba744 (feat)(chat-sdk) Optimize ChatMsg chart conditions (#2286)
Some checks failed
supersonic CentOS CI / build (21) (push) Has been cancelled
supersonic mac CI / build (21) (push) Has been cancelled
supersonic ubuntu CI / build (21) (push) Has been cancelled
supersonic windows CI / build (21) (push) Has been cancelled
2025-06-08 07:35:19 +08:00
FredTsang
9ffdba956e (feat)(chat-sdk) extract formatByDataFormatType() (#2287)
* feat(Chat/constants.ts): rm unused code

* feat(chat-sdk): extract formatByDataFormatType()
2025-06-08 07:34:51 +08:00
QJ_wonder
fa65b6eff7 Update NL2PluginParser.java 去除多数据集条件判断 2025-05-28 15:47:58 +08:00
QJ_wonder
0ab44c0866 Merge branch 'tencentmusic:master' into master_fixplugin 2025-05-27 14:54:49 +08:00
QJ_wonder
449fdf180f (fix)(chat)多个数据集也只保留召回的第一个结果
这里的多数据集其实对召回的结果没有影响,取第一个就好了
2025-05-27 14:42:34 +08:00
QJ_wonder
d275a145d5 Update WebServiceQuery.java 2025-05-27 14:07:32 +08:00
QJ_wonder
c8f690c1c2 (fix)(chat) 修改判断是否需要NL2SQL的条件判断
修改判断是否需要NL2SQL的条件判断,如果respone里已经有返回体,则不需要NL2SQL的执行,直接返回。避免插件返回体被NL2SQL返回体覆盖的问题
2025-05-27 13:39:48 +08:00
QJ_wonder
38af6e3a28 (fix)(chat) 修复插件调用问题
修复插件调用问题,将原有的Json解析替换成fastjson的json解析,并按String解析返回体,否则会报:Error while extracting response for type [class java.lang.Object] and content type [application/xml;charset=UTF-8]
2025-05-27 13:34:34 +08:00
11 changed files with 49 additions and 61 deletions

View File

@@ -88,10 +88,10 @@ public class WebServiceQuery extends PluginSemanticQuery {
restTemplate = ContextUtils.getBean(RestTemplate.class); restTemplate = ContextUtils.getBean(RestTemplate.class);
try { try {
responseEntity = responseEntity =
restTemplate.exchange(requestUrl, HttpMethod.POST, entity, Object.class); restTemplate.exchange(requestUrl, HttpMethod.POST, entity, String.class);
objectResponse = responseEntity.getBody(); objectResponse = responseEntity.getBody();
log.info("objectResponse:{}", objectResponse); log.info("objectResponse:{}", objectResponse);
Map<String, Object> response = JsonUtil.objectToMap(objectResponse); Map<String, Object> response = JSON.parseObject(objectResponse.toString());
webServiceResponse.setResult(response); webServiceResponse.setResult(response);
} catch (Exception e) { } catch (Exception e) {
log.info("Exception:{}", e.getMessage()); log.info("Exception:{}", e.getMessage());

View File

@@ -19,7 +19,7 @@ public class ParseContext {
} }
public boolean enableNL2SQL() { public boolean enableNL2SQL() {
return Objects.nonNull(agent) && agent.containsDatasetTool(); return Objects.nonNull(agent) && agent.containsDatasetTool()&&response.getSelectedParses().size() == 0;
} }
public boolean enableLLM() { public boolean enableLLM() {

View File

@@ -1,18 +1,3 @@
export const THEME_COLOR_LIST = [
'#3369FF',
'#36D2B8',
'#DB8D76',
'#47B359',
'#8545E6',
'#E0B18B',
'#7258F3',
'#0095FF',
'#52CC8F',
'#6675FF',
'#CC516E',
'#5CA9E6',
];
export enum SemanticTypeEnum { export enum SemanticTypeEnum {
MODEL = 'MODEL', MODEL = 'MODEL',
DIMENSION = 'DIMENSION', DIMENSION = 'DIMENSION',

View File

@@ -1,7 +1,7 @@
import { CHART_BLUE_COLOR, CHART_SECONDARY_COLOR, PREFIX_CLS } from '../../../common/constants'; import { CHART_BLUE_COLOR, CHART_SECONDARY_COLOR, PREFIX_CLS } from '../../../common/constants';
import { MsgDataType } from '../../../common/type'; import { MsgDataType } from '../../../common/type';
import { import {
formatByDecimalPlaces, formatByDataFormatType,
getChartLightenColor, getChartLightenColor,
getFormattedValue, getFormattedValue,
} from '../../../utils/utils'; } from '../../../utils/utils';
@@ -94,7 +94,7 @@ const BarChart: React.FC<Props> = ({
return value === 0 return value === 0
? 0 ? 0
: metricField.dataFormatType === 'percent' : metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces(metricField.dataFormat?.needMultiply100 ? +value * 100 : value, metricField.dataFormat?.decimalPlaces || 2)}%` ? formatByDataFormatType(value, metricField.dataFormatType, metricField.dataFormat)
: getFormattedValue(value); : getFormattedValue(value);
}, },
}, },
@@ -115,10 +115,7 @@ const BarChart: React.FC<Props> = ({
? '-' ? '-'
: metricField.dataFormatType === 'percent' || : metricField.dataFormatType === 'percent' ||
metricField.dataFormatType === 'decimal' metricField.dataFormatType === 'decimal'
? `${formatByDecimalPlaces( ? formatByDataFormatType(item.value, metricField.dataFormatType, metricField.dataFormat)
metricField.dataFormat?.needMultiply100 ? +item.value * 100 : item.value,
metricField.dataFormat?.decimalPlaces || 2
)}${metricField.dataFormatType === 'percent' ? '%' : ''}`
: getFormattedValue(item.value) : getFormattedValue(item.value)
}</span></div>` }</span></div>`
) )
@@ -151,7 +148,7 @@ const BarChart: React.FC<Props> = ({
return value === 0 return value === 0
? 0 ? 0
: metricField.dataFormatType === 'percent' : metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces(metricField.dataFormat?.needMultiply100 ? +value * 100 : value, metricField.dataFormat?.decimalPlaces || 2)}%` ? formatByDataFormatType(value, metricField.dataFormatType, metricField.dataFormat)
: getFormattedValue(value); : getFormattedValue(value);
}, },
}, },

View File

@@ -1,5 +1,5 @@
import { PREFIX_CLS } from '../../../common/constants'; import { PREFIX_CLS } from '../../../common/constants';
import { formatByDecimalPlaces, formatMetric, formatNumberWithCN } from '../../../utils/utils'; import { formatByDataFormatType, formatMetric, formatNumberWithCN } from '../../../utils/utils';
import ApplyAuth from '../ApplyAuth'; import ApplyAuth from '../ApplyAuth';
import { MsgDataType } from '../../../common/type'; import { MsgDataType } from '../../../common/type';
import PeriodCompareItem from './PeriodCompareItem'; import PeriodCompareItem from './PeriodCompareItem';
@@ -52,10 +52,7 @@ const MetricCard: React.FC<Props> = ({ data, question, loading, onApplyAuth }) =
{typeof value === 'string' && isNaN(+value) {typeof value === 'string' && isNaN(+value)
? value ? value
: dataFormatType === 'percent' || dataFormatType === 'decimal' : dataFormatType === 'percent' || dataFormatType === 'decimal'
? `${formatByDecimalPlaces( ? formatByDataFormatType(value, dataFormatType, dataFormat)
dataFormat?.needMultiply100 ? +value * 100 : value,
dataFormat?.decimalPlaces || 2
)}${dataFormatType === 'percent' ? '%' : ''}`
: isNumber : isNumber
? formatMetric(value) || '-' ? formatMetric(value) || '-'
: formatNumberWithCN(+value)} : formatNumberWithCN(+value)}

View File

@@ -1,5 +1,5 @@
import { PREFIX_CLS } from '../../../common/constants'; import { PREFIX_CLS } from '../../../common/constants';
import { formatByDecimalPlaces, formatMetric, formatNumberWithCN } from '../../../utils/utils'; import { formatByDataFormatType, formatMetric, formatNumberWithCN } from '../../../utils/utils';
import { AggregateInfoType, ColumnType } from '../../../common/type'; import { AggregateInfoType, ColumnType } from '../../../common/type';
import PeriodCompareItem from '../MetricCard/PeriodCompareItem'; import PeriodCompareItem from '../MetricCard/PeriodCompareItem';
import { SwapOutlined } from '@ant-design/icons'; import { SwapOutlined } from '@ant-design/icons';
@@ -29,10 +29,7 @@ const MetricInfo: React.FC<Props> = ({ aggregateInfo, currentMetricField }) => {
<div style={{ display: 'flex', alignItems: 'flex-end' }}> <div style={{ display: 'flex', alignItems: 'flex-end' }}>
<div className={`${prefixCls}-indicator-value`}> <div className={`${prefixCls}-indicator-value`}>
{dataFormatType === 'percent' || dataFormatType === 'decimal' {dataFormatType === 'percent' || dataFormatType === 'decimal'
? `${formatByDecimalPlaces( ? formatByDataFormatType(value, dataFormatType, dataFormat)
dataFormat?.needMultiply100 ? +value * 100 : value,
dataFormat?.decimalPlaces || 2
)}${dataFormatType === 'percent' ? '%' : ''}`
: isNumber : isNumber
? formatMetric(value) ? formatMetric(value)
: formatNumberWithCN(+value)} : formatNumberWithCN(+value)}

View File

@@ -1,6 +1,6 @@
import { CHART_SECONDARY_COLOR, CLS_PREFIX, THEME_COLOR_LIST } from '../../../common/constants'; import { CHART_SECONDARY_COLOR, CLS_PREFIX, THEME_COLOR_LIST } from '../../../common/constants';
import { import {
formatByDecimalPlaces, formatByDataFormatType,
getFormattedValue, getFormattedValue,
getMinMaxDate, getMinMaxDate,
groupByColumn, groupByColumn,
@@ -134,7 +134,7 @@ const MetricTrendChart: React.FC<Props> = ({
return value === 0 return value === 0
? 0 ? 0
: metricField.dataFormatType === 'percent' : metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces(value, metricField.dataFormat?.decimalPlaces || 2)}%` ? formatByDataFormatType(value, metricField.dataFormatType, metricField.dataFormat)
: getFormattedValue(value); : getFormattedValue(value);
}, },
}, },
@@ -156,10 +156,7 @@ const MetricTrendChart: React.FC<Props> = ({
? '-' ? '-'
: metricField.dataFormatType === 'percent' || : metricField.dataFormatType === 'percent' ||
metricField.dataFormatType === 'decimal' metricField.dataFormatType === 'decimal'
? `${formatByDecimalPlaces( ? formatByDataFormatType(item.value, metricField.dataFormatType, metricField.dataFormat)
item.value,
metricField.dataFormat?.decimalPlaces || 2
)}${metricField.dataFormatType === 'percent' ? '%' : ''}`
: getFormattedValue(item.value) : getFormattedValue(item.value)
}</span></div>` }</span></div>`
) )

View File

@@ -1,6 +1,6 @@
import { PREFIX_CLS, THEME_COLOR_LIST } from '../../../common/constants'; import { PREFIX_CLS, THEME_COLOR_LIST } from '../../../common/constants';
import { MsgDataType } from '../../../common/type'; import { MsgDataType } from '../../../common/type';
import { formatByDecimalPlaces, getFormattedValue } from '../../../utils/utils'; import { formatByDataFormatType, getFormattedValue } from '../../../utils/utils';
import type { ECharts } from 'echarts'; import type { ECharts } from 'echarts';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
@@ -55,10 +55,7 @@ const PieChart: React.FC<Props> = ({
const value = params.value; const value = params.value;
return `${params.name}: ${ return `${params.name}: ${
metricField.dataFormatType === 'percent' metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces( ? formatByDataFormatType(value, metricField.dataFormatType, metricField.dataFormat)
metricField.dataFormat?.needMultiply100 ? +value * 100 : value,
metricField.dataFormat?.decimalPlaces || 2
)}%`
: getFormattedValue(value) : getFormattedValue(value)
}`; }`;
}, },

View File

@@ -1,4 +1,4 @@
import { formatByDecimalPlaces, formatByThousandSeperator } from '../../../utils/utils'; import { formatByDataFormatType, formatByThousandSeperator } from '../../../utils/utils';
import { Table as AntTable } from 'antd'; import { Table as AntTable } from 'antd';
import { MsgDataType } from '../../../common/type'; import { MsgDataType } from '../../../common/type';
import { CLS_PREFIX } from '../../../common/constants'; import { CLS_PREFIX } from '../../../common/constants';
@@ -42,10 +42,7 @@ const Table: React.FC<Props> = ({ data, size, loading, question, onApplyAuth })
<div className={`${prefixCls}-formatted-value`}> <div className={`${prefixCls}-formatted-value`}>
{`${ {`${
value value
? formatByDecimalPlaces( ? formatByDataFormatType(value, dataFormatType, dataFormat)
dataFormat?.needMultiply100 ? +value * 100 : value,
dataFormat?.decimalPlaces || 2
)
: 0 : 0
}%`} }%`}
</div> </div>

View File

@@ -52,14 +52,14 @@ const ChatMsg: React.FC<Props> = ({
const prefixCls = `${PREFIX_CLS}-chat-msg`; const prefixCls = `${PREFIX_CLS}-chat-msg`;
const updateColummns = (queryColumnsValue: ColumnType[]) => { const updateColumns = (queryColumnsValue: ColumnType[]) => {
const referenceColumn = queryColumnsValue.find(item => item.showType === 'more'); const referenceColumn = queryColumnsValue.find(item => item.showType === 'more');
setReferenceColumn(referenceColumn); setReferenceColumn(referenceColumn);
setColumns(queryColumnsValue.filter(item => item.showType !== 'more')); setColumns(queryColumnsValue.filter(item => item.showType !== 'more'));
}; };
useEffect(() => { useEffect(() => {
updateColummns(queryColumns); updateColumns(queryColumns);
setDataSource(queryResults); setDataSource(queryResults);
setDefaultMetricField(chatContext?.metrics?.[0]); setDefaultMetricField(chatContext?.metrics?.[0]);
setActiveMetricField(chatContext?.metrics?.[0]); setActiveMetricField(chatContext?.metrics?.[0]);
@@ -115,26 +115,45 @@ const ChatMsg: React.FC<Props> = ({
metricFields.length > 0 && metricFields.length > 0 &&
categoryField.length <= 1 && categoryField.length <= 1 &&
!(metricFields.length > 1 && categoryField.length > 0) && !(metricFields.length > 1 && categoryField.length > 0) &&
!dataSource.every(item => item[dateField.bizName] === dataSource[0][dateField.bizName]); dataSource.some(item => item[dateField.bizName] !== dataSource[0][dateField.bizName]);
if (isMetricTrend) { if (isMetricTrend) {
return MsgContentTypeEnum.METRIC_TREND; return MsgContentTypeEnum.METRIC_TREND;
} }
/**
* For Pie Chart:
* 1. There should be at least one category field.
* 2. There should be exactly one metric field.
* 3. All metric values should be non-negative.
* 4. limit the number of data points based on device type:
* - For mobile devices, limit to 5 data points.
* - For desktop devices, limit to 10 data points.
*/
const isMetricPie = const isMetricPie =
categoryField.length > 0 && categoryField.length > 0 &&
metricFields?.length === 1 && metricFields?.length === 1 &&
(isMobile ? dataSource?.length <= 5 : dataSource?.length <= 10) && (isMobile ? dataSource?.length <= 5 : dataSource?.length <= 10) &&
dataSource.every(item => item[metricFields[0].bizName] > 0); dataSource.every(item => item[metricFields[0].bizName] >= 0);
if (isMetricPie) { if (isMetricPie) {
return MsgContentTypeEnum.METRIC_PIE; return MsgContentTypeEnum.METRIC_PIE;
} }
/**
* For Bar Chart:
* 1. There should be at least one category field.
* 2. There should be exactly one metric field.
* 3. The number of data points should be limited based on device type:
* - For mobile devices, limit to 5 data points.
* - For desktop devices, limit to 50 data points.
* 4. All metric values should be finite numbers.
*/
const isMetricBar = const isMetricBar =
categoryField?.length > 0 && categoryField?.length > 0 &&
metricFields?.length === 1 && metricFields?.length === 1 &&
(isMobile ? dataSource?.length <= 5 : dataSource?.length <= 50); (isMobile ? dataSource?.length <= 5 : dataSource?.length <= 50) &&
dataSource.every(item => isFinite(Number(item[metricFields[0].bizName])));
if (isMetricBar) { if (isMetricBar) {
return MsgContentTypeEnum.METRIC_BAR; return MsgContentTypeEnum.METRIC_BAR;
@@ -226,9 +245,6 @@ const ChatMsg: React.FC<Props> = ({
); );
case MsgContentTypeEnum.METRIC_PIE: case MsgContentTypeEnum.METRIC_PIE:
const categoryField = columns.find(item => item.showType === 'CATEGORY'); const categoryField = columns.find(item => item.showType === 'CATEGORY');
if (!categoryField) {
return null;
}
return ( return (
<Pie <Pie
data={{ ...data, queryColumns: columns, queryResults: dataSource }} data={{ ...data, queryColumns: columns, queryResults: dataSource }}
@@ -236,7 +252,7 @@ const ChatMsg: React.FC<Props> = ({
triggerResize={triggerResize} triggerResize={triggerResize}
loading={loading} loading={loading}
metricField={metricFields[0]} metricField={metricFields[0]}
categoryField={categoryField} categoryField={categoryField!}
/> />
); );
case MsgContentTypeEnum.MARKDOWN: case MsgContentTypeEnum.MARKDOWN:
@@ -266,7 +282,7 @@ const ChatMsg: React.FC<Props> = ({
}); });
setLoading(false); setLoading(false);
if (res.code === 200) { if (res.code === 200) {
updateColummns(res.data?.queryColumns || []); updateColumns(res.data?.queryColumns || []);
setDataSource(res.data?.queryResults || []); setDataSource(res.data?.queryResults || []);
} }
}; };

View File

@@ -1,6 +1,11 @@
import moment, { Moment } from 'moment'; import moment, { Moment } from 'moment';
import { NumericUnit } from '../common/constants'; import { NumericUnit } from '../common/constants';
import { isString } from 'lodash'; import { isString } from 'lodash';
import { ColumnType } from '../common/type';
export function formatByDataFormatType(value: number | string, type: ColumnType['dataFormatType'], dataFormat: Partial<ColumnType['dataFormat']> = {}) {
return `${formatByDecimalPlaces(dataFormat?.needMultiply100 ? +value * 100 : value, dataFormat?.decimalPlaces || 2)}${type === 'percent' ? '%' : ''}`;
}
export function formatByDecimalPlaces(value: number | string, decimalPlaces: number) { export function formatByDecimalPlaces(value: number | string, decimalPlaces: number) {
if (value === null || value === undefined || value === '') { if (value === null || value === undefined || value === '') {