[feature](webapp) upgrade chat version

This commit is contained in:
williamhliu
2023-06-30 17:42:03 +08:00
parent 8639c23dc4
commit 805a59dddd
69 changed files with 1570 additions and 842 deletions

View File

@@ -8,7 +8,7 @@ type Props = {
const Text: React.FC<Props> = ({ data }) => {
const prefixCls = `${PREFIX_CLS}-item`;
return (
<Message position="left" bubbleClassName={`${prefixCls}-text-bubble`} noWaterMark>
<Message position="left" bubbleClassName={`${prefixCls}-text-bubble`}>
<div className={`${prefixCls}-text`}>{data}</div>
</Message>
);

View File

@@ -1,49 +1,48 @@
import { MsgDataType, MsgValidTypeEnum, SuggestionDataType } from '../../common/type';
import { MsgDataType, MsgValidTypeEnum } from '../../common/type';
import { useEffect, useState } from 'react';
import Typing from './Typing';
import ChatMsg from '../ChatMsg';
import { querySuggestionInfo, chatQuery } from '../../service';
import { chatQuery } from '../../service';
import { MSG_VALID_TIP, PARSE_ERROR_TIP, PREFIX_CLS } from '../../common/constants';
import Text from './Text';
import Suggestion from '../Suggestion';
import Tools from '../Tools';
import SemanticDetail from '../SemanticDetail';
import IconFont from '../IconFont';
type Props = {
msg: string;
followQuestions?: string[];
conversationId?: number;
classId?: number;
domainId?: number;
isLastMessage?: boolean;
suggestionEnable?: boolean;
msgData?: MsgDataType;
onLastMsgDataLoaded?: (data: MsgDataType) => void;
isMobileMode?: boolean;
triggerResize?: boolean;
onMsgDataLoaded?: (data: MsgDataType) => void;
onSelectSuggestion?: (value: string) => void;
onUpdateMessageScroll?: () => void;
};
const ChatItem: React.FC<Props> = ({
msg,
followQuestions,
conversationId,
classId,
domainId,
isLastMessage,
suggestionEnable,
isMobileMode,
triggerResize,
msgData,
onLastMsgDataLoaded,
onMsgDataLoaded,
onSelectSuggestion,
onUpdateMessageScroll,
}) => {
const [data, setData] = useState<MsgDataType>();
const [suggestionData, setSuggestionData] = useState<SuggestionDataType>();
const [loading, setLoading] = useState(false);
const [metricInfoList, setMetricInfoList] = useState<any[]>([]);
const [tip, setTip] = useState('');
const setMsgData = (value: MsgDataType) => {
setData(value);
};
const updateData = (res: Result<MsgDataType>) => {
if (res.code === 401) {
if (res.code === 401 || res.code === 412) {
setTip(res.msg);
return false;
}
@@ -51,13 +50,13 @@ const ChatItem: React.FC<Props> = ({
setTip(PARSE_ERROR_TIP);
return false;
}
const { queryColumns, queryResults, queryState } = res.data || {};
const { queryColumns, queryResults, queryState, queryMode } = res.data || {};
if (queryState !== MsgValidTypeEnum.NORMAL && queryState !== MsgValidTypeEnum.EMPTY) {
setTip(MSG_VALID_TIP[queryState || MsgValidTypeEnum.INVALID]);
return false;
}
if (queryColumns && queryColumns.length > 0 && queryResults) {
setMsgData(res.data);
if ((queryColumns && queryColumns.length > 0 && queryResults) || queryMode === 'INSTRUCTION') {
setData(res.data);
setTip('');
return true;
}
@@ -65,41 +64,20 @@ const ChatItem: React.FC<Props> = ({
return false;
};
const updateSuggestionData = (semanticRes: MsgDataType, suggestionRes: any) => {
const { aggregateType, queryColumns, entityInfo } = semanticRes;
setSuggestionData({
currentAggregateType: aggregateType,
columns: queryColumns || [],
mainEntity: entityInfo,
suggestions: suggestionRes,
});
};
const getSuggestions = async (domainId: number, semanticResData: MsgDataType) => {
if (!domainId) {
return;
}
const res = await querySuggestionInfo(domainId);
updateSuggestionData(semanticResData, res.data.data);
};
const onSendMsg = async () => {
setLoading(true);
const semanticRes = await chatQuery(msg, conversationId, classId);
const semanticRes = await chatQuery(msg, conversationId, domainId);
updateData(semanticRes.data);
// if (suggestionEnable && semanticValid) {
// const semanticResData = semanticRes.data.data;
// await getSuggestions(semanticResData.entityInfo?.domainInfo?.itemId, semanticRes.data.data);
// } else {
// setSuggestionData(undefined);
// }
if (onLastMsgDataLoaded) {
onLastMsgDataLoaded(semanticRes.data.data);
if (onMsgDataLoaded) {
onMsgDataLoaded(semanticRes.data.data);
}
setLoading(false);
};
useEffect(() => {
if (data !== undefined) {
return;
}
if (msgData) {
updateData({ code: 200, data: msgData, msg: 'success' });
} else if (msg) {
@@ -107,15 +85,27 @@ const ChatItem: React.FC<Props> = ({
}
}, [msg, msgData]);
const prefixCls = `${PREFIX_CLS}-item`;
if (loading) {
return <Typing />;
return (
<div className={prefixCls}>
<IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />
<Typing />
</div>
);
}
if (tip) {
return <Text data={tip} />;
return (
<div className={prefixCls}>
<IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />
<Text data={tip} />
</div>
);
}
if (!data) {
if (!data || data.queryMode === 'INSTRUCTION') {
return null;
}
@@ -126,26 +116,33 @@ const ChatItem: React.FC<Props> = ({
}
};
const prefixCls = `${PREFIX_CLS}-item`;
return (
<div>
<ChatMsg data={data} onCheckMetricInfo={onCheckMetricInfo} />
<Tools isLastMessage={isLastMessage} />
{suggestionEnable && suggestionData && isLastMessage && (
<Suggestion {...suggestionData} onSelect={onSelectSuggestion} />
)}
<div className={`${prefixCls}-metric-info-list`}>
{metricInfoList.map(item => (
<SemanticDetail
dataSource={item}
onDimensionSelect={(value: string) => {
if (onSelectSuggestion) {
onSelectSuggestion(value);
}
}}
/>
))}
<div className={prefixCls}>
<IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />
<div className={`${prefixCls}-content`}>
<ChatMsg
question={msg}
followQuestions={followQuestions}
data={data}
isMobileMode={isMobileMode}
triggerResize={triggerResize}
onCheckMetricInfo={onCheckMetricInfo}
/>
<Tools data={data} isLastMessage={isLastMessage} isMobileMode={isMobileMode} />
{metricInfoList.length > 0 && (
<div className={`${prefixCls}-metric-info-list`}>
{metricInfoList.map(item => (
<SemanticDetail
dataSource={item}
onDimensionSelect={(value: string) => {
if (onSelectSuggestion) {
onSelectSuggestion(value);
}
}}
/>
))}
</div>
)}
</div>
</div>
);

View File

@@ -3,6 +3,26 @@
@chat-item-prefix-cls: ~'@{supersonic-chat-prefix}-item';
.@{chat-item-prefix-cls} {
display: flex;
&-avatar {
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
width: 40px;
height: 40px;
margin-right: 6px;
border-radius: 50%;
color: var(--chat-blue);
background-color: #fff;
}
&-content {
// flex: 1;
width: calc(100% - 50px);
}
&-metric-info-list {
margin-top: 30px;
display: flex;

View File

@@ -8,10 +8,11 @@ import NoPermissionChart from '../NoPermissionChart';
type Props = {
data: MsgDataType;
triggerResize?: boolean;
onApplyAuth?: (domain: string) => void;
};
const BarChart: React.FC<Props> = ({ data, onApplyAuth }) => {
const BarChart: React.FC<Props> = ({ data, triggerResize, onApplyAuth }) => {
const chartRef = useRef<any>();
const [instance, setInstance] = useState<ECharts>();
@@ -133,7 +134,13 @@ const BarChart: React.FC<Props> = ({ data, onApplyAuth }) => {
}
}, [queryResults]);
if (!metricColumn?.authorized) {
useEffect(() => {
if (triggerResize && instance) {
instance.resize();
}
}, [triggerResize]);
if (metricColumn && !metricColumn?.authorized) {
return (
<NoPermissionChart
domain={entityInfo?.domainInfo.name || ''}

View File

@@ -1,67 +1,55 @@
import { EntityInfoType, ChatContextType } from '../../../common/type';
import moment from 'moment';
import { PREFIX_CLS } from '../../../common/constants';
type Props = {
position: 'left' | 'right';
width?: number | string;
height?: number | string;
title?: string;
followQuestions?: string[];
bubbleClassName?: string;
noWaterMark?: boolean;
chatContext?: ChatContextType;
entityInfo?: EntityInfoType;
tip?: string;
aggregator?: string;
noTime?: boolean;
children?: React.ReactNode;
isMobileMode?: boolean;
};
const Message: React.FC<Props> = ({
position,
width,
height,
title,
followQuestions,
children,
bubbleClassName,
chatContext,
entityInfo,
aggregator,
noTime,
isMobileMode,
}) => {
const { aggType, dateInfo, filters, metrics, domainName } = chatContext || {};
const { dimensionFilters, domainName } = chatContext || {};
const prefixCls = `${PREFIX_CLS}-message`;
const timeSection =
!noTime && dateInfo?.text ? (
dateInfo.text
) : (
<div>{`${moment(dateInfo?.endDate).diff(dateInfo?.startDate, 'days') + 1}`}</div>
);
const entityInfoList =
entityInfo?.dimensions?.filter(dimension => !dimension.bizName.includes('photo')) || [];
const metricSection =
metrics &&
metrics.map((metric, index) => {
let metricNode = <span className={`${PREFIX_CLS}-metric`}>{metric.name}</span>;
return (
<>
{metricNode}
{index < metrics.length - 1 && <span></span>}
</>
);
});
const aggregatorSection = aggregator !== 'tag' && aggType !== 'NONE' && aggType;
const hasFilterSection = filters && filters.length > 0;
const hasFilterSection =
dimensionFilters && dimensionFilters.length > 0 && entityInfoList.length === 0;
const filterSection = hasFilterSection && (
<div className={`${prefixCls}-filter-section`}>
<div className={`${prefixCls}-field-name`}></div>
<div className={`${prefixCls}-filter-values`}>
{filters.map(filterItem => {
{dimensionFilters.map(filterItem => {
const filterValue =
typeof filterItem.value === 'string' ? [filterItem.value] : filterItem.value || [];
return (
<div className={`${prefixCls}-filter-item`} key={filterItem.name}>
{filterItem.name}{filterItem.value}
<div
className={`${prefixCls}-filter-item`}
key={filterItem.name}
title={filterValue.join('、')}
>
{filterItem.name}{filterValue.join('、')}
</div>
);
})}
@@ -69,14 +57,15 @@ const Message: React.FC<Props> = ({
</div>
);
const entityInfoList =
entityInfo?.dimensions?.filter(dimension => !dimension.bizName.includes('photo')) || [];
const hasEntityInfoSection =
entityInfoList.length > 0 && chatContext && chatContext.dimensions?.length > 0;
const leftTitle = title
? followQuestions && followQuestions.length > 0
? `多轮对话:${[title, ...followQuestions].join(' ← ')}`
: `单轮对话:${title}`
: '';
return (
<div className={prefixCls}>
{domainName && <div className={`${prefixCls}-domain-name`}>{domainName}</div>}
<div className={`${prefixCls}-content`}>
<div className={`${prefixCls}-body`}>
<div
@@ -86,31 +75,30 @@ const Message: React.FC<Props> = ({
e.stopPropagation();
}}
>
{position === 'left' && chatContext && (
<div className={`${prefixCls}-top-bar`}>
{domainName}
{/* {dimensionSection} */}
{timeSection}
{metricSection}
{aggregatorSection}
{/* {tipSection} */}
{position === 'left' && title && (
<div className={`${prefixCls}-top-bar`} title={leftTitle}>
{leftTitle}
</div>
)}
{(hasEntityInfoSection || hasFilterSection) && (
{(entityInfoList.length > 0 || hasFilterSection) && (
<div className={`${prefixCls}-info-bar`}>
{hasEntityInfoSection && (
{filterSection}
{entityInfoList.length > 0 && (
<div className={`${prefixCls}-main-entity-info`}>
{entityInfoList.slice(0, 3).map(dimension => {
{entityInfoList.slice(0, 4).map(dimension => {
return (
<div className={`${prefixCls}-info-item`} key={dimension.bizName}>
<div className={`${prefixCls}-info-name`}>{dimension.name}</div>
<div className={`${prefixCls}-info-value`}>{dimension.value}</div>
{dimension.bizName.includes('photo') ? (
<img width={40} height={40} src={dimension.value} alt="" />
) : (
<div className={`${prefixCls}-info-value`}>{dimension.value}</div>
)}
</div>
);
})}
</div>
)}
{filterSection}
</div>
)}
<div className={`${prefixCls}-children`}>{children}</div>

View File

@@ -3,6 +3,13 @@
@msg-prefix-cls: ~'@{supersonic-chat-prefix}-message';
.@{msg-prefix-cls} {
&-domain-name {
color: var(--text-color);
margin-bottom: 2px;
margin-left: 4px;
font-weight: 500;
}
&-content {
display: flex;
align-items: flex-start;
@@ -24,15 +31,14 @@
}
&-top-bar {
display: flex;
align-items: center;
position: relative;
max-width: 100%;
padding: 4px 0 8px;
overflow-x: auto;
color: var(--text-color);
font-weight: 500;
font-size: 14px;
color: var(--text-color-third);
font-size: 13px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
border-bottom: 1px solid rgba(0, 0, 0, 0.03);
}
@@ -44,11 +50,21 @@
font-size: 13px;
}
&-filter-values {
display: flex;
align-items: center;
column-gap: 6px;
}
&-filter-item {
padding: 2px 12px;
color: var(--text-color-secondary);
background-color: #edf2f2;
border-radius: 13px;
max-width: 200px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
&-tip {
@@ -58,10 +74,16 @@
&-info-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
row-gap: 12px;
flex-wrap: wrap;
margin-top: 20px;
column-gap: 20px;
color: var(--text-color-secondary);
background: rgba(133, 156, 241, 0.1);
padding: 4px 12px;
width: fit-content;
border-radius: 8px;
}
&-main-entity-info {
@@ -70,6 +92,7 @@
align-items: center;
font-size: 13px;
column-gap: 20px;
row-gap: 10px;
}
&-info-item {
@@ -77,12 +100,12 @@
align-items: center;
}
&-info-Name {
color: var(--text-color-fourth);
&-info-name {
color: var(--text-color-third);
}
&-info-value {
color: var(--text-color-secondary);
color: var(--text-color);
}
}

View File

@@ -22,7 +22,7 @@ const MetricCard: React.FC<Props> = ({ data, onApplyAuth }) => {
{/* <div className={`${prefixCls}-date-range`}>
{startTime === endTime ? startTime : `${startTime} ~ ${endTime}`}
</div> */}
{!indicatorColumn?.authorized ? (
{indicatorColumn && !indicatorColumn?.authorized ? (
<ApplyAuth domain={entityInfo?.domainInfo.name || ''} onApplyAuth={onApplyAuth} />
) : (
<div className={`${prefixCls}-indicator-value`}>

View File

@@ -19,6 +19,7 @@ type Props = {
categoryColumnName: string;
metricField: ColumnType;
resultList: any[];
triggerResize?: boolean;
onApplyAuth?: (domain: string) => void;
};
@@ -28,6 +29,7 @@ const MetricTrendChart: React.FC<Props> = ({
categoryColumnName,
metricField,
resultList,
triggerResize,
onApplyAuth,
}) => {
const chartRef = useRef<any>();
@@ -40,6 +42,7 @@ const MetricTrendChart: React.FC<Props> = ({
setInstance(instanceObj);
} else {
instanceObj = instance;
instanceObj.clear();
}
const valueColumnName = metricField.nameEn;
@@ -51,13 +54,13 @@ const MetricTrendChart: React.FC<Props> = ({
endDate &&
(dateColumnName.includes('date') || dateColumnName.includes('month'))
? normalizeTrendData(
groupDataValue[key],
dateColumnName,
valueColumnName,
startDate,
endDate,
dateColumnName.includes('month') ? 'months' : 'days'
)
groupDataValue[key],
dateColumnName,
valueColumnName,
startDate,
endDate,
dateColumnName.includes('month') ? 'months' : 'days'
)
: groupDataValue[key].reverse();
return result;
}, {});
@@ -114,8 +117,8 @@ const MetricTrendChart: React.FC<Props> = ({
return value === 0
? 0
: metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces(value, metricField.dataFormat?.decimalPlaces || 2)}%`
: getFormattedValue(value);
? `${formatByDecimalPlaces(value, metricField.dataFormat?.decimalPlaces || 2)}%`
: getFormattedValue(value);
},
},
},
@@ -135,11 +138,11 @@ const MetricTrendChart: React.FC<Props> = ({
item.value === ''
? '-'
: metricField.dataFormatType === 'percent'
? `${formatByDecimalPlaces(
? `${formatByDecimalPlaces(
item.value,
metricField.dataFormat?.decimalPlaces || 2
)}%`
: getFormattedValue(item.value)
: getFormattedValue(item.value)
}</span></div>`
)
.join('');
@@ -181,6 +184,12 @@ const MetricTrendChart: React.FC<Props> = ({
}
}, [resultList, metricField]);
useEffect(() => {
if (triggerResize && instance) {
instance.resize();
}
}, [triggerResize]);
const prefixCls = `${CLS_PREFIX}-metric-trend`;
return (

View File

@@ -1,30 +1,27 @@
import { useEffect, useState } from 'react';
import { CLS_PREFIX, DATE_TYPES } from '../../../common/constants';
import { ColumnType, MsgDataType } from '../../../common/type';
import { groupByColumn, isMobile } from '../../../utils/utils';
import { ColumnType, FieldType, MsgDataType } from '../../../common/type';
import { isMobile } from '../../../utils/utils';
import { queryData } from '../../../service';
import MetricTrendChart from './MetricTrendChart';
import classNames from 'classnames';
import { Spin } from 'antd';
import Table from '../Table';
import SemanticInfoPopover from '../SemanticInfoPopover';
type Props = {
data: MsgDataType;
triggerResize?: boolean;
onApplyAuth?: (domain: string) => void;
onCheckMetricInfo?: (data: any) => void;
};
const MetricTrend: React.FC<Props> = ({ data, onApplyAuth, onCheckMetricInfo }) => {
const MetricTrend: React.FC<Props> = ({ data, triggerResize, onApplyAuth, onCheckMetricInfo }) => {
const { queryColumns, queryResults, entityInfo, chatContext } = data;
const [columns, setColumns] = useState<ColumnType[]>(queryColumns);
const metricFields = columns.filter((column: any) => column.showType === 'NUMBER') || [];
const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER');
const [currentMetricField, setCurrentMetricField] = useState<ColumnType>(metricFields[0]);
const [onlyOneDate, setOnlyOneDate] = useState(false);
const [trendData, setTrendData] = useState(data);
const [activeMetricField, setActiveMetricField] = useState<FieldType>(chatContext.metrics?.[0]);
const [dataSource, setDataSource] = useState<any[]>(queryResults);
const [mergeMetric, setMergeMetric] = useState(false);
const [currentDateOption, setCurrentDateOption] = useState<number>();
const [loading, setLoading] = useState(false);
@@ -35,66 +32,17 @@ const MetricTrend: React.FC<Props> = ({ data, onApplyAuth, onCheckMetricInfo })
const categoryColumnName =
columns.find((column: any) => column.showType === 'CATEGORY')?.nameEn || '';
const getColumns = () => {
const categoryFieldData = groupByColumn(dataSource, categoryColumnName);
const result = [dateField];
const columnsValue = Object.keys(categoryFieldData).map(item => ({
authorized: currentMetricField.authorized,
name: item !== 'undefined' ? item : currentMetricField.name,
nameEn: `${item}${currentMetricField.name}`,
showType: 'NUMBER',
type: 'NUMBER',
}));
return result.concat(columnsValue);
};
const getResultList = () => {
return [
{
[dateField.nameEn]: dataSource[0][dateField.nameEn],
...dataSource.reduce((result, item) => {
result[`${item[categoryColumnName]}${currentMetricField.name}`] =
item[currentMetricField.nameEn];
return result;
}, {}),
},
];
};
useEffect(() => {
setDataSource(queryResults);
}, [queryResults]);
useEffect(() => {
let onlyOneDateValue = false;
let dataValue = trendData;
if (dateColumnName && dataSource.length > 0) {
const dateFieldData = groupByColumn(dataSource, dateColumnName);
onlyOneDateValue =
Object.keys(dateFieldData).length === 1 && Object.keys(dateFieldData)[0] !== undefined;
if (onlyOneDateValue) {
if (categoryColumnName !== '') {
dataValue = {
...trendData,
queryColumns: getColumns(),
queryResults: getResultList(),
};
} else {
setMergeMetric(true);
}
}
}
setOnlyOneDate(onlyOneDateValue);
setTrendData(dataValue);
}, [currentMetricField]);
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES[0];
const dateOptions = DATE_TYPES[chatContext.dateInfo?.period] || DATE_TYPES[0];
const onLoadData = async (value: number) => {
const onLoadData = async (value: any) => {
setLoading(true);
const { data } = await queryData({
...chatContext,
dateInfo: { ...chatContext.dateInfo, unit: value },
...value,
});
setLoading(false);
if (data.code === 200) {
@@ -105,20 +53,21 @@ const MetricTrend: React.FC<Props> = ({ data, onApplyAuth, onCheckMetricInfo })
const selectDateOption = (dateOption: number) => {
setCurrentDateOption(dateOption);
// const { domainName, dimensions, metrics, aggType, filters } = chatContext || {};
// const dimensionSection = dimensions?.join('、') || '';
// const metricSection = metrics?.join('、') || '';
// const aggregatorSection = aggType || '';
// const filterSection = filters
// .reduce((result, dimensionName) => {
// result = result.concat(dimensionName);
// return result;
// }, [])
// .join('、');
onLoadData(dateOption);
onLoadData({
metrics: [activeMetricField],
dateInfo: { ...chatContext?.dateInfo, unit: dateOption },
});
};
if (metricFields.length === 0) {
const onSwitchMetric = (metricField: FieldType) => {
setActiveMetricField(metricField);
onLoadData({
dateInfo: { ...chatContext.dateInfo, unit: currentDateOption || chatContext.dateInfo.unit },
metrics: [metricField],
});
};
if (!currentMetricField) {
return null;
}
@@ -127,64 +76,66 @@ const MetricTrend: React.FC<Props> = ({ data, onApplyAuth, onCheckMetricInfo })
return (
<div className={prefixCls}>
<div className={`${prefixCls}-charts`}>
{!onlyOneDate && (
<div className={`${prefixCls}-date-options`}>
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => {
const dateOptionClass = classNames(`${prefixCls}-date-option`, {
[`${prefixCls}-date-active`]: dateOption.value === currentDateOption,
[`${prefixCls}-date-mobile`]: isMobile,
});
return (
<>
<div
key={dateOption.value}
className={dateOptionClass}
onClick={() => {
selectDateOption(dateOption.value);
}}
>
{dateOption.label}
{dateOption.value === currentDateOption && (
<div className={`${prefixCls}-active-identifier`} />
)}
</div>
{index !== dateOptions.length - 1 && (
<div className={`${prefixCls}-date-option-divider`} />
<div className={`${prefixCls}-date-options`}>
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => {
const dateOptionClass = classNames(`${prefixCls}-date-option`, {
[`${prefixCls}-date-active`]: dateOption.value === currentDateOption,
[`${prefixCls}-date-mobile`]: isMobile,
});
return (
<>
<div
key={dateOption.value}
className={dateOptionClass}
onClick={() => {
selectDateOption(dateOption.value);
}}
>
{dateOption.label}
{dateOption.value === currentDateOption && (
<div className={`${prefixCls}-active-identifier`} />
)}
</>
);
})}
</div>
)}
{metricFields.length > 1 && !mergeMetric && (
</div>
{index !== dateOptions.length - 1 && (
<div className={`${prefixCls}-date-option-divider`} />
)}
</>
);
})}
</div>
{chatContext.metrics.length > 0 && (
<div className={`${prefixCls}-metric-fields`}>
{metricFields.map((metricField: ColumnType) => {
{chatContext.metrics.map((metricField: FieldType) => {
const metricFieldClass = classNames(`${prefixCls}-metric-field`, {
[`${prefixCls}-metric-field-active`]:
currentMetricField?.nameEn === metricField.nameEn,
activeMetricField?.bizName === metricField.bizName &&
chatContext.metrics.length > 1,
[`${prefixCls}-metric-field-single`]: chatContext.metrics.length === 1,
});
return (
<div
className={metricFieldClass}
key={metricField.nameEn}
key={metricField.bizName}
onClick={() => {
setCurrentMetricField(metricField);
if (chatContext.metrics.length > 1) {
onSwitchMetric(metricField);
}
}}
>
<SemanticInfoPopover
{/* <SemanticInfoPopover
classId={chatContext.domainId}
uniqueId={metricField.nameEn}
uniqueId={metricField.bizName}
onDetailBtnClick={onCheckMetricInfo}
>
{metricField.name}
</SemanticInfoPopover>
> */}
{metricField.name}
{/* </SemanticInfoPopover> */}
</div>
);
})}
</div>
)}
{onlyOneDate ? (
<Table data={trendData} onApplyAuth={onApplyAuth} />
{dataSource?.length === 1 ? (
<Table data={data} onApplyAuth={onApplyAuth} />
) : (
<Spin spinning={loading}>
<MetricTrendChart
@@ -193,6 +144,7 @@ const MetricTrend: React.FC<Props> = ({ data, onApplyAuth, onCheckMetricInfo })
categoryColumnName={categoryColumnName}
metricField={currentMetricField}
resultList={dataSource}
triggerResize={triggerResize}
onApplyAuth={onApplyAuth}
/>
</Spin>

View File

@@ -81,6 +81,17 @@
background-color: var(--chat-blue);
}
&-metric-field-single {
padding-left: 0;
font-weight: 500;
cursor: default;
color: var(--text-color-secondary);
&:hover {
color: var(--text-color-secondary);
}
}
&-date-options {
display: flex;
align-items: center;

View File

@@ -56,14 +56,21 @@ const Table: React.FC<Props> = ({ data, onApplyAuth }) => {
}
);
const getRowClassName = (_: any, index: number) => {
return index % 2 !== 0 ? `${prefixCls}-even-row` : '';
};
return (
<div className={prefixCls}>
<AntTable
pagination={queryResults.length <= 10 ? false : undefined}
size={queryResults.length === 1 ? 'middle' : 'small'}
pagination={
queryResults.length <= 10 ? false : { defaultPageSize: 10, position: ['bottomCenter'] }
}
columns={tableColumns}
dataSource={queryResults}
style={{ width: '100%' }}
scroll={{ x: 'max-content' }}
rowClassName={getRowClassName}
/>
</div>
);

View File

@@ -16,6 +16,10 @@
width: 100%;
}
&-even-row {
background-color: #fbfbfb;
}
.ant-table-container table > thead > tr:first-child th:first-child {
border-top-left-radius: 12px !important;
border-bottom-left-radius: 12px !important;

View File

@@ -7,12 +7,23 @@ import Table from './Table';
import { MsgDataType } from '../../common/type';
type Props = {
question: string;
followQuestions?: string[];
data: MsgDataType;
isMobileMode?: boolean;
triggerResize?: boolean;
onCheckMetricInfo?: (data: any) => void;
};
const ChatMsg: React.FC<Props> = ({ data, onCheckMetricInfo }) => {
const { aggregateType, queryColumns, queryResults, chatContext, entityInfo } = data;
const ChatMsg: React.FC<Props> = ({
question,
followQuestions,
data,
isMobileMode,
triggerResize,
onCheckMetricInfo,
}) => {
const { queryColumns, queryResults, chatContext, entityInfo, queryMode } = data;
if (!queryColumns || !queryResults) {
return null;
@@ -24,20 +35,30 @@ const ChatMsg: React.FC<Props> = ({ data, onCheckMetricInfo }) => {
const metricFields = queryColumns.filter(item => item.showType === 'NUMBER');
const getMsgContent = () => {
if (categoryField.length > 1 || aggregateType === 'tag') {
if (
categoryField.length > 1 ||
queryMode === 'ENTITY_DETAIL' ||
queryMode === 'ENTITY_DIMENSION'
) {
return <Table data={data} />;
}
if (dateField && metricFields.length > 0) {
return <MetricTrend data={data} onCheckMetricInfo={onCheckMetricInfo} />;
return (
<MetricTrend
data={data}
triggerResize={triggerResize}
onCheckMetricInfo={onCheckMetricInfo}
/>
);
}
if (singleData) {
return <MetricCard data={data} />;
}
return <Bar data={data} />;
return <Bar data={data} triggerResize={triggerResize} />;
};
let width = '100%';
if ((categoryField.length > 1 || aggregateType === 'tag') && !isMobile) {
if (categoryField.length > 1 && !isMobile && !isMobileMode) {
if (queryColumns.length === 1) {
width = '600px';
} else if (queryColumns.length === 2) {
@@ -50,8 +71,9 @@ const ChatMsg: React.FC<Props> = ({ data, onCheckMetricInfo }) => {
position="left"
chatContext={chatContext}
entityInfo={entityInfo}
aggregator={aggregateType}
tip={''}
title={question}
followQuestions={followQuestions}
isMobileMode={isMobileMode}
width={width}
>
{getMsgContent()}

View File

@@ -0,0 +1,7 @@
import { createFromIconfontCN } from '@ant-design/icons';
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4120566_hsbqfckf8tl.js',
});
export default IconFont;

View File

@@ -17,7 +17,7 @@ const SemanticDetail: React.FC<Props> = ({ dataSource, onDimensionSelect }) => {
const semanticDetailCls = `${CLS_PREFIX}-semantic-detail`;
return (
<Message position="left" width="100%" noTime>
<Message position="left" width="100%">
<div>
<div>
<Row>

View File

@@ -79,7 +79,7 @@ const Suggestion: React.FC<Props> = ({
return (
<div className={suggestionClass}>
<Message position="left" width="fit-content" noWaterMark>
<Message position="left" width="fit-content">
<div className={`${prefixCls}-tip`}></div>
{metricList.length > 0 && (
<div className={`${prefixCls}-content-section`}>

View File

@@ -2,12 +2,15 @@ import { isMobile } from '../../utils/utils';
import { DislikeOutlined, LikeOutlined } from '@ant-design/icons';
import { Button, message } from 'antd';
import { CLS_PREFIX } from '../../common/constants';
import { MsgDataType } from '../../common/type';
type Props = {
data: MsgDataType;
isLastMessage?: boolean;
isMobileMode?: boolean;
};
const Tools: React.FC<Props> = ({ isLastMessage }) => {
const Tools: React.FC<Props> = ({ data, isLastMessage, isMobileMode }) => {
const prefixCls = `${CLS_PREFIX}-tools`;
const changeChart = () => {
@@ -18,10 +21,6 @@ const Tools: React.FC<Props> = ({ isLastMessage }) => {
message.info('正在开发中,敬请期待');
};
const lockDomain = () => {
message.info('正在开发中,敬请期待');
};
const like = () => {
message.info('正在开发中,敬请期待');
};
@@ -30,12 +29,6 @@ const Tools: React.FC<Props> = ({ isLastMessage }) => {
message.info('正在开发中,敬请期待');
};
const lockDomainSection = isLastMessage && (
<Button shape="round" onClick={lockDomain}>
</Button>
);
const feedbackSection = isLastMessage && (
<div className={`${prefixCls}-feedback`}>
<div></div>
@@ -44,25 +37,19 @@ const Tools: React.FC<Props> = ({ isLastMessage }) => {
</div>
);
if (isMobile) {
return (
<div className={`${prefixCls}-mobile-tools`}>
{isLastMessage && <div className={`${prefixCls}-tools`}>{lockDomainSection}</div>}
{feedbackSection}
</div>
);
}
return (
<div className={prefixCls}>
<Button shape="round" onClick={changeChart}>
</Button>
<Button shape="round" onClick={addToDashboard}>
</Button>
{lockDomainSection}
{feedbackSection}
{!isMobile && !isMobileMode && (
<>
<Button shape="round" onClick={changeChart}>
</Button>
<Button shape="round" onClick={addToDashboard}>
</Button>
{feedbackSection}
</>
)}
</div>
);
};