import Bar from './Bar'; import MetricCard from './MetricCard'; import MetricTrend from './MetricTrend'; import MarkDown from './MarkDown'; import Table from './Table'; import { ColumnType, DrillDownDimensionType, FieldType, MsgDataType } from '../../common/type'; import { useEffect, useState } from 'react'; import { queryData } from '../../service'; import classNames from 'classnames'; import { PREFIX_CLS, MsgContentTypeEnum } from '../../common/constants'; import Text from './Text'; import DrillDownDimensions from '../DrillDownDimensions'; import MetricOptions from '../MetricOptions'; import { isMobile } from '../../utils/utils'; import Pie from './Pie'; type Props = { queryId?: number; question: string; data: MsgDataType; chartIndex: number; triggerResize?: boolean; forceShowTable?: boolean; isSimpleMode?: boolean; onMsgContentTypeChange: (msgContentType: MsgContentTypeEnum) => void; }; const ChatMsg: React.FC = ({ queryId, question, data, chartIndex, triggerResize, forceShowTable = false, isSimpleMode, onMsgContentTypeChange, }) => { const { queryColumns, queryResults, chatContext, queryMode } = data || {}; const { dimensionFilters, elementMatches } = chatContext || {}; const [columns, setColumns] = useState([]); const [referenceColumn, setReferenceColumn] = useState(); const [dataSource, setDataSource] = useState(queryResults); const [drillDownDimension, setDrillDownDimension] = useState(); const [secondDrillDownDimension, setSecondDrillDownDimension] = useState(); const [loading, setLoading] = useState(false); const [defaultMetricField, setDefaultMetricField] = useState(); const [activeMetricField, setActiveMetricField] = useState(); const [dateModeValue, setDateModeValue] = useState(); const [currentDateOption, setCurrentDateOption] = useState(); const prefixCls = `${PREFIX_CLS}-chat-msg`; const updateColummns = (queryColumnsValue: ColumnType[]) => { const referenceColumn = queryColumnsValue.find(item => item.showType === 'more'); setReferenceColumn(referenceColumn); setColumns(queryColumnsValue.filter(item => item.showType !== 'more')); }; useEffect(() => { updateColummns(queryColumns); setDataSource(queryResults); setDefaultMetricField(chatContext?.metrics?.[0]); setActiveMetricField(chatContext?.metrics?.[0]); setDateModeValue(chatContext?.dateInfo?.dateMode); setCurrentDateOption(chatContext?.dateInfo?.unit); setDrillDownDimension(undefined); setSecondDrillDownDimension(undefined); }, [data]); const metricFields = columns.filter(item => item.showType === 'NUMBER'); const getMsgContentType = () => { const singleData = dataSource.length === 1; const dateField = columns.find(item => item.showType === 'DATE' || item.type === 'DATE'); const categoryField = columns.filter(item => item.showType === 'CATEGORY'); const metricFields = columns.filter(item => item.showType === 'NUMBER'); if (!columns) { return; } if (isSimpleMode) { return MsgContentTypeEnum.MARKDOWN; } if (forceShowTable) { return MsgContentTypeEnum.TABLE; } const isDslMetricCard = queryMode === 'LLM_S2SQL' && singleData && metricFields.length === 1 && columns.length === 1; const isMetricCard = (queryMode.includes('METRIC') || isDslMetricCard) && singleData; const isText = !queryColumns?.length; if (isText) { return MsgContentTypeEnum.TEXT; } if (isMetricCard) { return MsgContentTypeEnum.METRIC_CARD; } const isTable = !isText && !isMetricCard && (categoryField.length > 1 || queryMode === 'TAG_DETAIL' || queryMode === 'ENTITY_DIMENSION' || dataSource?.length === 1 || (categoryField.length === 1 && metricFields.length === 0)); if (isTable) { return MsgContentTypeEnum.TABLE; } const isMetricTrend = dateField && metricFields.length > 0 && categoryField.length <= 1 && !(metricFields.length > 1 && categoryField.length > 0) && !dataSource.every(item => item[dateField.bizName] === dataSource[0][dateField.bizName]); if (isMetricTrend) { return MsgContentTypeEnum.METRIC_TREND; } const isMetricPie = categoryField.length > 0 && metricFields?.length === 1 && (isMobile ? dataSource?.length <= 5 : dataSource?.length <= 10) && dataSource.every(item => item[metricFields[0].bizName] > 0); if (isMetricPie) { return MsgContentTypeEnum.METRIC_PIE; } const isMetricBar = categoryField?.length > 0 && metricFields?.length === 1 && (isMobile ? dataSource?.length <= 5 : dataSource?.length <= 50); if (isMetricBar) { return MsgContentTypeEnum.METRIC_BAR; } return MsgContentTypeEnum.TABLE; }; const getMsgStyle = (type: MsgContentTypeEnum) => { if (isMobile) { return { maxWidth: 'calc(100vw - 20px)' }; } if (!queryResults?.length || !queryColumns.length) { return; } if (type === MsgContentTypeEnum.METRIC_BAR) { return { [queryResults.length > 5 ? 'width' : 'minWidth']: queryResults.length * 150, }; } if (type === MsgContentTypeEnum.TABLE) { return { [queryColumns.length > 5 ? 'width' : 'minWidth']: queryColumns.length * 150, }; } if (type === MsgContentTypeEnum.METRIC_TREND || type === MsgContentTypeEnum.METRIC_PIE) { return { width: 'calc(100vw - 410px)' }; } }; useEffect(() => { const type = getMsgContentType(); if (type) { onMsgContentTypeChange?.(type); } }, [data, columns, isSimpleMode]); if (!queryColumns || !queryResults || !columns) { return null; } const getMsgContent = () => { const contentType = getMsgContentType(); switch (contentType) { case MsgContentTypeEnum.TEXT: return ; case MsgContentTypeEnum.METRIC_CARD: return ( ); case MsgContentTypeEnum.TABLE: return ( ); case MsgContentTypeEnum.METRIC_TREND: return ( ); case MsgContentTypeEnum.METRIC_BAR: return ( ); case MsgContentTypeEnum.METRIC_PIE: const categoryField = columns.find(item => item.showType === 'CATEGORY'); if (!categoryField) { return null; } return ( ); case MsgContentTypeEnum.MARKDOWN: return (
); default: return (
); } }; const onLoadData = async (value: any) => { setLoading(true); const res: any = await queryData({ ...chatContext, ...value, queryId, parseId: chatContext.id, }); setLoading(false); if (res.code === 200) { updateColummns(res.data?.queryColumns || []); setDataSource(res.data?.queryResults || []); } }; const onSelectDimension = async (dimension?: DrillDownDimensionType) => { setLoading(true); setDrillDownDimension(dimension); onLoadData({ dateInfo: { ...chatContext.dateInfo, dateMode: dateModeValue, unit: currentDateOption || chatContext.dateInfo?.unit, }, dimensions: dimension ? [...(chatContext.dimensions || []), dimension] : chatContext.dimensions, metrics: [activeMetricField || defaultMetricField], }); }; const onSelectSecondDimension = (dimension?: DrillDownDimensionType) => { setSecondDrillDownDimension(dimension); onLoadData({ dateInfo: { ...chatContext.dateInfo, dateMode: dateModeValue, unit: currentDateOption || chatContext.dateInfo?.unit, }, dimensions: [ ...(chatContext.dimensions || []), ...(drillDownDimension ? [drillDownDimension] : []), ...(dimension ? [dimension] : []), ], metrics: [activeMetricField || defaultMetricField], }); }; const onSwitchMetric = (metricField?: FieldType) => { setActiveMetricField(metricField); onLoadData({ dateInfo: { ...chatContext.dateInfo, dateMode: dateModeValue, unit: currentDateOption || chatContext.dateInfo?.unit, }, dimensions: drillDownDimension ? [...(chatContext.dimensions || []), drillDownDimension] : chatContext.dimensions, metrics: [metricField || defaultMetricField], }); }; const selectDateOption = (dateOption: number) => { setCurrentDateOption(dateOption); setDateModeValue('RECENT'); onLoadData({ metrics: [activeMetricField || defaultMetricField], dimensions: drillDownDimension ? [...(chatContext.dimensions || []), drillDownDimension] : chatContext.dimensions, dateInfo: { ...chatContext?.dateInfo, dateMode: 'RECENT', unit: dateOption, }, }); }; const chartMsgClass = classNames({ [prefixCls]: ![MsgContentTypeEnum.TABLE, MsgContentTypeEnum.MARKDOWN].includes( getMsgContentType() as MsgContentTypeEnum ), }); const entityId = dimensionFilters?.length > 0 ? dimensionFilters[0].value : undefined; const entityName = elementMatches?.find((item: any) => item.element?.type === 'ID')?.element ?.name; const isEntityMode = (queryMode === 'TAG_LIST_FILTER' || queryMode === 'METRIC_TAG') && typeof entityId === 'string' && entityName !== undefined; const existDrillDownDimension = queryMode.includes('METRIC') && getMsgContentType() !== MsgContentTypeEnum.TEXT && !isEntityMode; const recommendMetrics = chatContext?.metrics?.filter(metric => queryColumns.every(queryColumn => queryColumn.bizName !== metric.bizName) ); const isMultipleMetric = (queryMode.includes('METRIC') || queryMode === 'LLM_S2SQL') && recommendMetrics?.length > 0 && queryColumns?.filter(column => column.showType === 'NUMBER').length === 1; const type = getMsgContentType(); const style = type ? getMsgStyle(type) : undefined; return (
{dataSource?.length === 0 ? (
暂无数据
) : (
{getMsgContent()} {(isMultipleMetric || existDrillDownDimension) && !isSimpleMode && (
{isMultipleMetric && ( )} {existDrillDownDimension && ( )}
)}
)}
); }; export default ChatMsg;