diff --git a/webapp/packages/chat-sdk/package.json b/webapp/packages/chat-sdk/package.json index 79169865b..614b9f771 100644 --- a/webapp/packages/chat-sdk/package.json +++ b/webapp/packages/chat-sdk/package.json @@ -192,4 +192,4 @@ "engines": { "node": ">=14.18.0" } -} \ No newline at end of file +} diff --git a/webapp/packages/chat-sdk/src/common/type.ts b/webapp/packages/chat-sdk/src/common/type.ts index 276a4970e..f46716b71 100644 --- a/webapp/packages/chat-sdk/src/common/type.ts +++ b/webapp/packages/chat-sdk/src/common/type.ts @@ -46,9 +46,10 @@ export type DateInfoType = { export type FilterItemType = { elementID: number; name: string; + bizName: string; operator: string; type: string; - value: string[]; + value: string; }; export type ModelType = { @@ -62,6 +63,8 @@ export type ModelType = { } export type ChatContextType = { + id: number; + queryId: number; aggType: string; modelId: number; modelName: string; @@ -69,7 +72,7 @@ export type ChatContextType = { dateInfo: DateInfoType; dimensions: FieldType[]; metrics: FieldType[]; - entity: { alias: string[] }; + entity: { alias: string[], id: number }; elementMatches: any[]; queryMode: string; dimensionFilters: FilterItemType[]; @@ -126,6 +129,7 @@ export enum ParseStateEnum { export type ParseDataType = { chatId: number; + queryId: number; queryText: string; state: ParseStateEnum; selectedParses: ChatContextType[]; diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/ExecuteItem.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/ExecuteItem.tsx index e7f23c9f2..93865b1d3 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/ExecuteItem.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/ExecuteItem.tsx @@ -1,13 +1,14 @@ import { Spin } from 'antd'; +import { CheckCircleFilled } from '@ant-design/icons'; import { PREFIX_CLS } from '../../common/constants'; import { MsgDataType } from '../../common/type'; import ChatMsg from '../ChatMsg'; -import Tools from '../Tools'; -import Text from './Text'; -import Typing from './Typing'; +import WebPage from '../ChatMsg/WebPage'; +import Loading from './Loading'; type Props = { question: string; + queryId?: number; executeLoading: boolean; entitySwitchLoading: boolean; chartIndex: number; @@ -15,13 +16,12 @@ type Props = { data?: MsgDataType; isMobileMode?: boolean; triggerResize?: boolean; - isLastMessage?: boolean; - onSwitchEntity: (entityId: string) => void; onChangeChart: () => void; }; const ExecuteItem: React.FC = ({ question, + queryId, executeLoading, entitySwitchLoading, chartIndex, @@ -29,49 +29,59 @@ const ExecuteItem: React.FC = ({ data, isMobileMode, triggerResize, - isLastMessage, - onSwitchEntity, onChangeChart, }) => { const prefixCls = `${PREFIX_CLS}-item`; + const getNodeTip = (title: string, tip?: string) => { + return ( + <> +
+ +
+ {title} + {!tip && } +
+
+ {tip &&
{tip}
} + + ); + }; + if (executeLoading) { - return ; + return getNodeTip('数据查询中'); } if (executeTip) { - return ; + return getNodeTip('数据查询失败', executeTip); } - if (!data || data.queryMode === 'WEB_PAGE') { + if (!data) { return null; } - const isMetricCard = - (data.queryMode === 'METRIC_DOMAIN' || data.queryMode === 'METRIC_FILTER') && - data.queryResults?.length === 1; - return ( -
- - - - {!isMetricCard && ( - - )} -
+ <> +
+ +
数据查询结果
+
+
+ + {data?.queryMode === 'WEB_PAGE' ? ( + + ) : ( + + )} + +
+ ); }; diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/Loading.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/Loading.tsx new file mode 100644 index 000000000..b5fbfec57 --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/ChatItem/Loading.tsx @@ -0,0 +1,14 @@ +import { PREFIX_CLS } from '../../common/constants'; + +const Loading = () => { + const prefixCls = `${PREFIX_CLS}-item`; + return ( + + + + + + ); +}; + +export default Loading; diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/ParseTip.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/ParseTip.tsx index 5d20b35ac..179180f36 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/ParseTip.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/ParseTip.tsx @@ -1,9 +1,11 @@ import React, { ReactNode } from 'react'; import { AGG_TYPE_MAP, PREFIX_CLS } from '../../common/constants'; import { ChatContextType } from '../../common/type'; -import Text from './Text'; -import Typing from './Typing'; +import { CheckCircleFilled, InfoCircleOutlined } from '@ant-design/icons'; import classNames from 'classnames'; +import SwicthEntity from './SwitchEntity'; +import { Tooltip } from 'antd'; +import Loading from './Loading'; type Props = { parseLoading: boolean; @@ -12,6 +14,7 @@ type Props = { currentParseInfo?: ChatContextType; optionMode?: boolean; onSelectParseInfo: (parseInfo: ChatContextType) => void; + onSwitchEntity: (entityId: string) => void; }; const MAX_OPTION_VALUES_COUNT = 2; @@ -23,15 +26,34 @@ const ParseTip: React.FC = ({ currentParseInfo, optionMode, onSelectParseInfo, + onSwitchEntity, }) => { const prefixCls = `${PREFIX_CLS}-item`; + const getNode = (tipTitle: string, tipNode?: ReactNode, parseSucceed?: boolean) => { + const contentContainerClass = classNames(`${prefixCls}-content-container`, { + [`${prefixCls}-content-container-succeed`]: parseSucceed, + }); + return ( +
+
+ +
+ {tipTitle} + {!tipNode && } +
+
+ {tipNode &&
{tipNode}
} +
+ ); + }; + if (parseLoading) { - return ; + return getNode('意图解析中'); } if (parseTip) { - return ; + return getNode('意图解析失败', parseTip); } if (parseInfoOptions.length === 0) { @@ -81,6 +103,45 @@ const ParseTip: React.FC = ({ const fields = queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems; + const getFilterContent = (filters: any) => { + return ( +
+ {filters.map((filter: any, index: number) => ( +
+ + {filter.name} + {filter.operator !== '=' ? ` ${filter.operator} ` : ':'} + + {Array.isArray(filter.value) ? filter.value.join('、') : filter.value} + {index !== filters.length - 1 && } +
+ ))} +
+ ); + }; + + const getFiltersNode = () => { + return ( +
+
筛选条件:
+ MAX_OPTION_VALUES_COUNT + ? getFilterContent(dimensionFilters) + : '' + } + color="#fff" + overlayStyle={{ maxWidth: 'none' }} + > +
+ {getFilterContent(dimensionFilters.slice(0, MAX_OPTION_VALUES_COUNT))} + {dimensionFilters.length > MAX_OPTION_VALUES_COUNT && ' ...'} +
+
+
+ ); + }; + return (
= ({ }} > {index !== undefined &&
{index + 1}.
} - {!!agentType ? ( + {!!agentType && queryMode !== 'DSL' ? (
将由{agentType === 'plugin' ? '插件' : '内置'}工具 {agentName}来解答
) : ( <> - {queryMode.includes('ENTITY') && + {(queryMode.includes('ENTITY') || queryMode === 'DSL') && typeof entityId === 'string' && !!entityAlias && !!entityName ? (
{entityAlias}:
-
{entityName}
+ {!isOptions && (entityAlias === '歌曲' || entityAlias === '艺人') ? ( + + ) : ( +
{entityName}
+ )}
) : (
@@ -112,7 +181,7 @@ const ParseTip: React.FC = ({
{modelName}
)} - {metric && ( + {queryMode !== 'ENTITY_ID' && metric && (
指标:
{metric.name}
@@ -120,7 +189,7 @@ const ParseTip: React.FC = ({ )} {!isOptions && (
-
时间:
+
数据时间:
{startDate === endDate ? startDate : `${startDate} ~ ${endDate}`}
@@ -148,23 +217,11 @@ const ParseTip: React.FC = ({ 'ENTITY_DETAIL', 'ENTITY_LIST_FILTER', 'ENTITY_ID', + 'DSL', ].includes(queryMode) && dimensionFilters && - dimensionFilters?.length > 0 && ( -
-
筛选条件:
- {dimensionFilters.slice(0, MAX_OPTION_VALUES_COUNT).map((filter, index) => ( -
- {filter.name}: - - {Array.isArray(filter.value) ? filter.value.join('、') : filter.value} - - {index !== dimensionFilters.length - 1 && } -
- ))} - {dimensionFilters.length > MAX_OPTION_VALUES_COUNT && '...'} -
- )} + dimensionFilters?.length > 0 && + getFiltersNode()} {queryMode === 'METRIC_ORDERBY' && aggType && aggType !== 'NONE' && (
聚合方式:
@@ -191,16 +248,29 @@ const ParseTip: React.FC = ({
); } else { - const agentType = parseInfoOptions[0]?.properties?.type; + const { type } = parseInfoOptions[0]?.properties || {}; + const entityAlias = parseInfoOptions[0]?.entity?.alias?.[0]?.split('.')?.[0]; + const entityName = parseInfoOptions[0]?.elementMatches?.find( + item => item.element?.type === 'ID' + )?.element.name; + const queryMode = parseInfoOptions[0]?.queryMode; + tipNode = (
-
{!!agentType ? '您的问题' : '您的问题解析为:'}
{getTipNode(parseInfoOptions[0])} + {(!type || queryMode === 'DSL') && entityAlias && entityName && ( +
+ +
+ 如果未匹配到您查询的{entityAlias},可点击上面的{entityAlias}名切换 +
+
+ )}
); } - return ; + return getNode('意图解析结果', tipNode, true); }; export default ParseTip; diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/SwitchEntity.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/SwitchEntity.tsx new file mode 100644 index 000000000..46bb1f42e --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/ChatItem/SwitchEntity.tsx @@ -0,0 +1,52 @@ +import { useState } from 'react'; +import { ChatContextType } from '../../common/type'; +import { Popover } from 'antd'; +import { DownOutlined } from '@ant-design/icons'; +import RecommendOptions from '../RecommendOptions'; +import { PREFIX_CLS } from '../../common/constants'; + +type Props = { + entityName: string; + chatContext: ChatContextType; + onSwitchEntity: (entityId: string) => void; +}; + +const SwicthEntity: React.FC = ({ entityName, chatContext, onSwitchEntity }) => { + const [recommendOptionsOpen, setRecommendOptionsOpen] = useState(false); + const { modelId, modelName, dimensionFilters } = chatContext || {}; + + const prefixCls = `${PREFIX_CLS}-item`; + + const switchEntity = (option: string) => { + setRecommendOptionsOpen(false); + onSwitchEntity(option); + }; + + const entityId = dimensionFilters?.find( + filter => filter?.bizName === 'zyqk_song_id' || filter?.bizName === 'singer_id' + )?.value; + + return ( + + } + placement="bottomLeft" + trigger="click" + open={recommendOptionsOpen} + onOpenChange={open => setRecommendOptionsOpen(open)} + > +
+ {entityName} + +
+
+ ); +}; + +export default SwicthEntity; diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx index 55f8466a6..a92395582 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx @@ -7,6 +7,7 @@ import ParseTip from './ParseTip'; import ExecuteItem from './ExecuteItem'; import { isMobile } from '../../utils/utils'; import classNames from 'classnames'; +import Tools from '../Tools'; type Props = { msg: string; @@ -17,6 +18,7 @@ type Props = { isLastMessage?: boolean; msgData?: MsgDataType; isMobileMode?: boolean; + isHistory?: boolean; triggerResize?: boolean; parseOptions?: ChatContextType[]; onMsgDataLoaded?: (data: MsgDataType, valid: boolean) => void; @@ -31,6 +33,7 @@ const ChatItem: React.FC = ({ filter, isLastMessage, isMobileMode, + isHistory, triggerResize, msgData, parseOptions, @@ -118,15 +121,12 @@ const ChatItem: React.FC = ({ const { data: parseData } = await chatParse(msg, conversationId, modelId, agentId, filter); setParseLoading(false); const { code, data } = parseData || {}; - const { state, selectedParses } = data || {}; + const { state, selectedParses, queryId } = data || {}; if ( code !== 200 || state === ParseStateEnum.FAILED || - selectedParses == null || - selectedParses.length === 0 || - (selectedParses.length > 0 && - !selectedParses[0]?.properties?.type && - !selectedParses[0]?.queryMode) + !selectedParses?.length || + (!selectedParses[0]?.properties?.type && !selectedParses[0]?.queryMode) ) { setParseTip(PARSE_ERROR_TIP); return; @@ -134,10 +134,14 @@ const ChatItem: React.FC = ({ if (onUpdateMessageScroll) { onUpdateMessageScroll(); } - setParseInfoOptions(selectedParses || []); - const parseInfoValue = selectedParses[0]; + const parseInfos = selectedParses.map(item => ({ + ...item, + queryId, + })); + setParseInfoOptions(parseInfos || []); + const parseInfoValue = parseInfos[0]; setParseInfo(parseInfoValue); - onExecute(parseInfoValue, selectedParses); + onExecute(parseInfoValue, parseInfos); }; useEffect(() => { @@ -158,6 +162,9 @@ const ChatItem: React.FC = ({ const res = await switchEntity(entityId, data?.chatContext?.modelId, conversationId || 0); setEntitySwitchLoading(false); setData(res.data.data); + const { chatContext } = res.data.data; + setParseInfo(chatContext); + setParseInfoOptions([chatContext]); }; const onChangeChart = () => { @@ -176,10 +183,14 @@ const ChatItem: React.FC = ({ [`${prefixCls}-content-mobile`]: isMobile, }); + const isMetricCard = + (data?.queryMode === 'METRIC_DOMAIN' || data?.queryMode === 'METRIC_FILTER') && + data?.queryResults?.length === 1; + return (
-
- {!isMobile && } + {!isMobile && } +
= ({ currentParseInfo={parseInfo} optionMode={parseOptions !== undefined} onSelectParseInfo={onSelectParseInfo} + onSwitchEntity={onSwitchEntity} /> -
-
- {executeMode && data?.queryMode !== 'WEB_PAGE' && ( -
- {!isMobile && } -
+ {executeMode && ( -
+ )}
- )} + {!isMetricCard && data && ( + + )} +
); }; diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/style.less b/webapp/packages/chat-sdk/src/components/ChatItem/style.less index a74c784c0..85ad02042 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatItem/style.less @@ -4,23 +4,144 @@ .@{chat-item-prefix-cls} { display: flex; - flex-direction: column; - row-gap: 20px; width: 100%; - &-section { - width: 100%; + &-loading { + display: inline-block; + width: 60px; + height: 20px; + } + + &-loading-dot { + display: inline-block; + width: 4px; + height: 4px; + // border-radius: 50%; + background-color: var(--text-color); + margin: 0 2px; + opacity: 0; + animation: dot 1s ease-in-out infinite; + } + + &-loading-dot:nth-child(1) { + animation-delay: 0s; + } + + &-loading-dot:nth-child(2) { + animation-delay: 0.2s; + } + + &-loading-dot:nth-child(3) { + animation-delay: 0.4s; + } + + @keyframes dot { + 0% { + opacity: 0; + transform: scale(0.5); + } + 50% { + opacity: 1; + transform: scale(1); + } + 100% { + opacity: 0; + transform: scale(0.5); + } + } + + &-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; + } + + &-mobile-msg-card { + width: 100%; + } + + &-msg-card { + flex: 1; + } + + &-content { + position: relative; + box-sizing: border-box; + min-width: 1px; + max-width: 100%; + padding: 12px 16px; + background: #fff; + border: 1px solid transparent; + border-radius: 12px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12); + } + + &-content-mobile { + width: 100%; } &-content-text { margin-top: 12px; } - &-msg-content { - width: 100%; + &-title-bar { display: flex; - flex-direction: column; + align-items: center; + column-gap: 10px; + } + + &-step-icon { + color: var(--green); + font-size: 16px; + } + + &-content-container { + margin: 2px 0 2px 7px; + padding: 6px 0 4px 18px; + } + + &-content-container-succeed { + border-left: 1px solid var(--green); + padding-bottom: 10px; + } + + &-switch-entity-tip { + display: flex; + align-items: center; + column-gap: 6px; + margin-top: 4px; + color: var(--text-color-third); + font-size: 13px; + } + + &-switch-entity { + cursor: pointer; + } + + &-down-icon { + margin-left: 4px; + color: var(--text-color-fourth); + font-size: 12px; + } + + &-last-node { + border-left: none; + margin-left: 0; + padding-left: 0; + } + + &-chart-content { + padding: 6px 14px 12px; + border: 1px solid var(--border-color-base); + border-radius: 4px; + background: #f5f8fb; } &-multi-options { @@ -39,10 +160,10 @@ &-tip { display: flex; - align-items: center; + flex-direction: column; row-gap: 6px; flex-wrap: wrap; - color: var(--text-color); + color: var(--text-color-third); } &-tip-content { @@ -51,7 +172,7 @@ flex-wrap: wrap; row-gap: 6px; column-gap: 12px; - color: var(--text-color); + color: var(--text-color-third); } &-tip-content-option { @@ -86,6 +207,16 @@ align-items: center; } + &-tip-item-content { + display: flex; + align-items: center; + } + + &-tip-item-filter-content { + display: flex; + align-items: center; + } + &-mode-name { margin-right: -10px; font-weight: 500; @@ -99,27 +230,6 @@ &-tip-item-option { font-weight: 500; } - - &-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 { - width: calc(100% - 50px); - } - - &-content-mobile { - width: 100%; - } &-metric-info-list { margin-top: 30px; diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx index c858acf2f..09bbae8f7 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx @@ -159,7 +159,7 @@ const BarChart: React.FC = ({ ); } - const hasFilterSection = dimensionFilters?.length > 0; + // const hasFilterSection = dimensionFilters?.length > 0; const prefixCls = `${PREFIX_CLS}-bar`; @@ -167,11 +167,11 @@ const BarChart: React.FC = ({
{metricColumn?.name}
- {(hasFilterSection || drillDownDimension) && ( + {drillDownDimension && (
(
- + {/* */} {drillDownDimension && (
下钻维度:
@@ -183,13 +183,13 @@ const BarChart: React.FC = ({
)}
- {dateInfo && ( + {/* {dateInfo && (
{dateInfo.startDate === dateInfo.endDate ? dateInfo.startDate : `${dateInfo.startDate} ~ ${dateInfo.endDate}`}
- )} + )} */}
diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx index 1cf240d3c..9887c2224 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx @@ -1,12 +1,13 @@ import { PREFIX_CLS } from '../../../common/constants'; -import { formatMetric } from '../../../utils/utils'; +import { formatMetric, formatNumberWithCN } from '../../../utils/utils'; import ApplyAuth from '../ApplyAuth'; import { DrillDownDimensionType, MsgDataType } from '../../../common/type'; import PeriodCompareItem from './PeriodCompareItem'; import DrillDownDimensions from '../../DrillDownDimensions'; import { Spin } from 'antd'; import classNames from 'classnames'; -import FilterSection from '../FilterSection'; +import { SwapOutlined } from '@ant-design/icons'; +import { useState } from 'react'; type Props = { data: MsgDataType; @@ -26,7 +27,7 @@ const MetricCard: React.FC = ({ const { queryMode, queryColumns, queryResults, entityInfo, aggregateInfo, chatContext } = data; const { metricInfos } = aggregateInfo || {}; - const { dateInfo, dimensionFilters } = chatContext || {}; + const { dateInfo } = chatContext || {}; const { startDate } = dateInfo || {}; const indicatorColumn = queryColumns?.find(column => column.showType === 'NUMBER'); @@ -34,25 +35,31 @@ const MetricCard: React.FC = ({ const prefixCls = `${PREFIX_CLS}-metric-card`; + const matricCardClass = classNames(prefixCls, { + [`${PREFIX_CLS}-metric-card-dsl`]: queryMode === 'DSL', + }); + const indicatorClass = classNames(`${prefixCls}-indicator`, { [`${prefixCls}-indicator-period-compare`]: metricInfos?.length > 0, }); - const hasFilterSection = dimensionFilters?.length > 0; + const [isNumber, setIsNumber] = useState(false); + const handleNumberClick = () => { + setIsNumber(!isNumber); + }; return ( -
+
{indicatorColumn?.name ? (
{indicatorColumn?.name}
) : ( -
+
)} - {(hasFilterSection || drillDownDimension) && ( + {drillDownDimension && (
(
- {drillDownDimension && (
下钻维度:
@@ -70,8 +77,15 @@ const MetricCard: React.FC = ({ {indicatorColumn && !indicatorColumn?.authorized ? ( ) : ( -
- {formatMetric(queryResults?.[0]?.[indicatorColumnName]) || '-'} +
+
+ {isNumber + ? formatMetric(queryResults?.[0]?.[indicatorColumnName]) || '-' + : formatNumberWithCN(+queryResults?.[0]?.[indicatorColumnName])} +
+
+ +
)} {metricInfos?.length > 0 && ( @@ -90,7 +104,6 @@ const MetricCard: React.FC = ({ dimensionFilters={chatContext.dimensionFilters} drillDownDimension={drillDownDimension} onSelectDimension={onSelectDimension} - isMetricCard />
)} diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less index 3fa55d1d9..3dbbc8ae6 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less @@ -4,9 +4,13 @@ .@{metric-card-prefix-cls} { width: 100%; - height: 130px; + height: 162px; row-gap: 4px; + &-dsl { + height: 90px; + } + &-top-bar { display: flex; align-items: baseline; @@ -73,6 +77,13 @@ color: var(--chat-blue); } + &-indicator-switch { + color: var(--text-color-fourth); + font-size: 18px; + margin-left: 6px; + margin-bottom: 3px; + } + &-period-compare { width: 100%; display: flex; @@ -108,8 +119,6 @@ } &-drill-down-dimensions { - position: absolute; - bottom: -44px; - left: -16; + margin-top: 2px; } } \ No newline at end of file diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricInfo.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricInfo.tsx index acf636869..359f63f42 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricInfo.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricInfo.tsx @@ -1,7 +1,9 @@ import { PREFIX_CLS } from '../../../common/constants'; -import { formatMetric } from '../../../utils/utils'; +import { formatMetric, formatNumberWithCN } from '../../../utils/utils'; import { AggregateInfoType } from '../../../common/type'; import PeriodCompareItem from '../MetricCard/PeriodCompareItem'; +import { SwapOutlined } from '@ant-design/icons'; +import { useState } from 'react'; type Props = { aggregateInfo: AggregateInfoType; @@ -14,18 +16,34 @@ const MetricInfo: React.FC = ({ aggregateInfo }) => { const prefixCls = `${PREFIX_CLS}-metric-info`; + const [isNumber, setIsNumber] = useState(false); + const handleNumberClick = () => { + setIsNumber(!isNumber); + }; + return (
-
{date}
-
{formatMetric(value)}
- {metricInfos?.length > 0 && ( -
- {Object.keys(statistics).map((key: any) => ( - - ))} +
+
+ {isNumber ? formatMetric(value) : formatNumberWithCN(+value)}
- )} +
+ +
+
+
+
+ 最新数据日期:{date} +
+ {metricInfos?.length > 0 && ( +
+ {Object.keys(statistics).map((key: any) => ( + + ))} +
+ )} +
); diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricTrendChart.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricTrendChart.tsx index a57046883..9c2bc9ff6 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricTrendChart.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricTrendChart.tsx @@ -12,6 +12,7 @@ import React, { useEffect, useRef, useState } from 'react'; import moment from 'moment'; import { ColumnType } from '../../../common/type'; import NoPermissionChart from '../NoPermissionChart'; +import classNames from 'classnames'; type Props = { model?: string; @@ -201,12 +202,16 @@ const MetricTrendChart: React.FC = ({ const prefixCls = `${CLS_PREFIX}-metric-trend`; + const flowTrendChartClass = classNames(`${prefixCls}-flow-trend-chart`, { + [`${prefixCls}-flow-trend-chart-single`]: !categoryColumnName, + }); + return (
{!metricField.authorized ? ( ) : ( -
+
)}
); diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx index a70c2db0b..e53052f32 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx @@ -9,7 +9,7 @@ import { Spin } from 'antd'; import Table from '../Table'; import DrillDownDimensions from '../../DrillDownDimensions'; import MetricInfo from './MetricInfo'; -import FilterSection from '../FilterSection'; +import MetricOptions from '../../MetricOptions'; type Props = { data: MsgDataType; @@ -25,6 +25,7 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY; const [columns, setColumns] = useState([]); + const [defaultMetricField, setDefaultMetricField] = useState(); const [activeMetricField, setActiveMetricField] = useState(); const [dataSource, setDataSource] = useState([]); const [currentDateOption, setCurrentDateOption] = useState(); @@ -58,7 +59,9 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply })?.value; setColumns(queryColumns || []); - setActiveMetricField(chatContext?.metrics?.[0]); + const metricField = chatContext?.metrics?.[0]; + setDefaultMetricField(metricField); + setActiveMetricField(metricField); setDataSource(queryResults); setCurrentDateOption(initialDateOption); setDimensions(chatContext?.dimensions); @@ -107,7 +110,7 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply }); }; - const onSwitchMetric = (metricField: FieldType) => { + const onSwitchMetric = (metricField?: FieldType) => { setActiveMetricField(metricField); onLoadData({ dateInfo: { @@ -116,7 +119,7 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply unit: currentDateOption || chatContext.dateInfo.unit, }, dimensions: drillDownDimension ? [...(dimensions || []), drillDownDimension] : undefined, - metrics: [metricField], + metrics: [metricField || defaultMetricField], }); }; @@ -139,44 +142,25 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply return null; } - const prefixCls = `${CLS_PREFIX}-metric-trend`; + const isMultipleMetric = chatContext?.metrics?.length > 1; + const existDrillDownDimension = queryMode.includes('METRIC') && !isEntityMode; - const hasFilterSection = dimensionFilters?.length > 0; + const prefixCls = `${CLS_PREFIX}-metric-trend`; return (
- {chatContext.metrics.length > 0 && ( -
- {chatContext.metrics.slice(0, 5).map((metricField: FieldType) => { - const metricFieldClass = classNames(`${prefixCls}-metric-field`, { - [`${prefixCls}-metric-field-active`]: - activeMetricField?.bizName === metricField.bizName && - chatContext.metrics.length > 1, - [`${prefixCls}-metric-field-single`]: chatContext.metrics.length === 1, - }); - return ( -
{ - if (chatContext.metrics.length > 1) { - onSwitchMetric(metricField); - } - }} - > - {metricField.name} -
- ); - })} -
- )} - {(hasFilterSection || drillDownDimension) && ( +
+ {activeMetricField?.name} +
+ {drillDownDimension && (
(
- {drillDownDimension && (
下钻维度:
@@ -192,7 +176,7 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply
- {aggregateInfoValue?.metricInfos?.length > 0 && ( + {!isMobile && aggregateInfoValue?.metricInfos?.length > 0 && ( )}
@@ -236,13 +220,25 @@ const MetricTrend: React.FC = ({ data, chartIndex, triggerResize, onApply /> )}
- {queryMode.includes('METRIC') && !isEntityMode && ( - + {(isMultipleMetric || existDrillDownDimension) && ( +
+ {isMultipleMetric && ( + + )} + {existDrillDownDimension && ( + + )} +
)}
diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less index 07a193f2b..1b7a4d543 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less @@ -24,6 +24,7 @@ display: flex; align-items: center; color: var(--text-color-third); + margin-left: 4px; } &-filter-section { @@ -58,7 +59,7 @@ &-indicator { display: flex; flex-direction: column; - align-items: flex-start; + align-items: baseline; justify-content: center; } @@ -83,11 +84,15 @@ height: 230px; } + &-flow-trend-chart-single { + height: 180px; + } + &-charts { display: flex; flex-direction: column; width: 100%; - row-gap: 12px; + row-gap: 4px; } &-metric-fields { @@ -123,11 +128,6 @@ } } - &-metric-field-active { - color: #fff !important; - background-color: var(--chat-blue); - } - &-metric-field-single { padding-left: 0; font-weight: 500; @@ -165,6 +165,13 @@ font-size: 12px; } + &-bottom-tools { + display: flex; + align-items: center; + column-gap: 20px; + font-size: 14px; + } + &-active-identifier { position: absolute; bottom: -6px; @@ -184,14 +191,8 @@ .@{metric-info-prefix-cls} { &-indicator { display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: flex-start; - } - - &-date { - color: var(--text-color-fourth); - font-size: 12px; + align-items: baseline; + column-gap: 12px; } &-indicator-value { @@ -203,12 +204,33 @@ color: var(--text-color-secondary); } - &-period-compare { - width: 100%; + &-bottom-section { + display: flex; + align-items: center; + column-gap: 20px; + margin-top: 4px; + } + + &-date { + color: var(--text-color-fourth); + font-size: 13px; + } + + &-date-value { + color: var(--chat-blue); + } + + &-indicator-switch { + color: var(--text-color-fourth); + font-size: 18px; + margin-left: 6px; + margin-bottom: 3px; + } + + &-period-compare { display: flex; align-items: center; column-gap: 20px; - margin-top: 2px; font-size: 13px; overflow-x: auto; } diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx index dee0a0f7e..1fddac097 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx @@ -68,8 +68,7 @@ const Table: React.FC = ({ data, size, onApplyAuth }) => { } columns={tableColumns} dataSource={queryResults} - style={{ width: '100%' }} - // scroll={{ x: 'max-content' }} + style={{ width: '100%', overflowX: 'auto' }} rowClassName={getRowClassName} size={size} /> diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/style.less index 9c0707ee4..778d2c766 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/style.less @@ -3,8 +3,7 @@ @table-prefix-cls: ~'@{supersonic-chat-prefix}-table'; .@{table-prefix-cls} { - margin-top: 16px; - margin-bottom: 20px; + margin-top: 6px; &-photo { display: flex; @@ -68,9 +67,13 @@ .ant-table-tbody { .ant-table-cell { - padding: 15px 0; - color: #333; + padding: 12px 2px; + color: var(--text-color); font-size: 14px; } } + + .ant-table-pagination.ant-pagination { + margin-bottom: 0; + } } \ No newline at end of file diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/WebPage/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/WebPage/index.tsx new file mode 100644 index 000000000..b53563733 --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/WebPage/index.tsx @@ -0,0 +1,128 @@ +import { useCallback, useEffect, useState } from 'react'; +import { CLS_PREFIX } from '../../../common/constants'; +import { MsgDataType } from '../../../common/type'; +import { isProd } from '../../../utils/utils'; + +type Props = { + id: string | number; + data: MsgDataType; +}; + +const DEFAULT_HEIGHT = 800; + +const WebPage: React.FC = ({ id, data }) => { + const [pluginUrl, setPluginUrl] = useState(''); + const [height, setHeight] = useState(DEFAULT_HEIGHT); + + const prefixCls = `${CLS_PREFIX}-web-page`; + + const { + name, + webPage: { url, params }, + } = data.response || {}; + + const handleMessage = useCallback((event: MessageEvent) => { + const messageData = event.data; + const { type, payload } = messageData; + if (type === 'changeMiniProgramContainerSize') { + const { msgId, height } = payload; + if (`${msgId}` === `${id}`) { + setHeight(height); + // updateMessageContainerScroll(); + } + return; + } + if (messageData === 'storyResize') { + const ifr: any = document.getElementById(`reportIframe_${id}`); + const iDoc = ifr.contentDocument || ifr.document || ifr.contentWindow; + setTimeout(() => { + setHeight(isProd() ? calcPageHeight(iDoc) : DEFAULT_HEIGHT); + }, 200); + return; + } + }, []); + + useEffect(() => { + window.addEventListener('message', handleMessage); + return () => { + window.removeEventListener('message', handleMessage); + }; + }, [handleMessage]); + + function calcPageHeight(doc: any) { + const titleAreaEl = doc.getElementById('titleArea'); + const titleAreaHeight = Math.max( + titleAreaEl?.clientHeight || 0, + titleAreaEl?.scrollHeight || 0 + ); + const dashboardGridEl = doc.getElementsByClassName('dashboardGrid')?.[0]; + const dashboardGridHeight = Math.max( + dashboardGridEl?.clientHeight || 0, + dashboardGridEl?.scrollHeight || 0 + ); + return Math.max(titleAreaHeight + dashboardGridHeight + 10, DEFAULT_HEIGHT); + } + + const initData = () => { + const heightValue = + params?.find((option: any) => option.paramType === 'FORWARD' && option.key === 'height') + ?.value || DEFAULT_HEIGHT; + setHeight(heightValue); + let urlValue = url; + const valueParams = (params || []) + .filter((option: any) => option.paramType !== 'FORWARD') + .reduce((result: any, item: any) => { + result[item.key] = item.value; + return result; + }, {}); + if (urlValue.includes('?type=dashboard') || urlValue.includes('?type=widget')) { + const filterData = encodeURIComponent( + JSON.stringify( + urlValue.includes('dashboard') + ? { + global: valueParams, + } + : { + local: valueParams, + } + ) + ); + urlValue = urlValue.replace( + '?', + `?miniProgram=true&reportName=${name}&filterData=${filterData}&` + ); + urlValue = + !isProd() && !urlValue.includes('http') ? `http://s2.tmeoa.com${urlValue}` : urlValue; + } else { + const params = Object.keys(valueParams || {}).map(key => `${key}=${valueParams[key]}`); + if (params.length > 0) { + if (url.includes('?')) { + urlValue = urlValue.replace('?', `?${params.join('&')}&`); + } else { + urlValue = `${urlValue}?${params.join('&')}`; + } + } + } + // onReportLoaded(heightValue + 190); + setPluginUrl(urlValue); + }; + + useEffect(() => { + initData(); + }, []); + + return ( + //
+