diff --git a/webapp/packages/chat-sdk/package.json b/webapp/packages/chat-sdk/package.json index de19d7f0b..732194368 100644 --- a/webapp/packages/chat-sdk/package.json +++ b/webapp/packages/chat-sdk/package.json @@ -191,4 +191,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 fcbedf03a..69468cdc9 100644 --- a/webapp/packages/chat-sdk/src/common/type.ts +++ b/webapp/packages/chat-sdk/src/common/type.ts @@ -80,9 +80,21 @@ export type InstructionResonseType = { name: string; } +export type MetricInfoType = { + date: string; + name: string; + statistics: any; + value: string; +} + +export type AggregateInfoType = { + metricInfos: MetricInfoType[] +} + export type MsgDataType = { id: number; question: string; + aggregateInfo: AggregateInfoType; chatContext: ChatContextType; entityInfo: EntityInfoType; queryAuthorization: any; @@ -90,7 +102,7 @@ export type MsgDataType = { queryResults: any[]; queryId: number; queryMode: string; - queryState: MsgValidTypeEnum; + queryState: string; response: InstructionResonseType; }; @@ -147,7 +159,7 @@ export type SuggestionDataType = { export type HistoryMsgItemType = { questionId: number; queryText: string; - queryResponse: MsgDataType; + queryResult: MsgDataType; chatId: number; createTime: string; feedback: string; @@ -158,3 +170,10 @@ export type HistoryType = { hasNextPage: boolean; list: HistoryMsgItemType[]; }; + +export type DrillDownDimensionType = { + id: number; + domain: number; + name: string; + bizName: string; +} \ No newline at end of file diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx index 961cff6c7..8dfe17a3d 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx @@ -1,12 +1,11 @@ -import { MsgDataType, MsgValidTypeEnum } from '../../common/type'; +import { MsgDataType } from '../../common/type'; import { useEffect, useState } from 'react'; import Typing from './Typing'; import ChatMsg from '../ChatMsg'; import { chatQuery } from '../../service'; -import { MSG_VALID_TIP, PARSE_ERROR_TIP, PREFIX_CLS } from '../../common/constants'; +import { PARSE_ERROR_TIP, PREFIX_CLS } from '../../common/constants'; import Text from './Text'; import Tools from '../Tools'; -import SemanticDetail from '../SemanticDetail'; import IconFont from '../IconFont'; type Props = { @@ -19,8 +18,6 @@ type Props = { isMobileMode?: boolean; triggerResize?: boolean; onMsgDataLoaded?: (data: MsgDataType) => void; - onSelectSuggestion?: (value: string) => void; - onUpdateMessageScroll?: () => void; }; const ChatItem: React.FC = ({ @@ -33,12 +30,9 @@ const ChatItem: React.FC = ({ triggerResize, msgData, onMsgDataLoaded, - onSelectSuggestion, - onUpdateMessageScroll, }) => { const [data, setData] = useState(); const [loading, setLoading] = useState(false); - const [metricInfoList, setMetricInfoList] = useState([]); const [tip, setTip] = useState(''); const updateData = (res: Result) => { @@ -51,8 +45,8 @@ const ChatItem: React.FC = ({ return false; } const { queryColumns, queryResults, queryState, queryMode } = res.data || {}; - if (queryState !== MsgValidTypeEnum.NORMAL && queryState !== MsgValidTypeEnum.EMPTY) { - setTip(MSG_VALID_TIP[queryState || MsgValidTypeEnum.INVALID]); + if (queryState !== 'SUCCESS') { + setTip(PARSE_ERROR_TIP); return false; } if ((queryColumns && queryColumns.length > 0 && queryResults) || queryMode === 'INSTRUCTION') { @@ -109,12 +103,9 @@ const ChatItem: React.FC = ({ return null; } - const onCheckMetricInfo = (data: any) => { - setMetricInfoList([...metricInfoList, data]); - if (onUpdateMessageScroll) { - onUpdateMessageScroll(); - } - }; + const isMetricCard = + (data.queryMode === 'METRIC_DOMAIN' || data.queryMode === 'METRIC_FILTER') && + data.queryResults?.length === 1; return (
@@ -126,22 +117,9 @@ const ChatItem: React.FC = ({ data={data} isMobileMode={isMobileMode} triggerResize={triggerResize} - onCheckMetricInfo={onCheckMetricInfo} /> - - {metricInfoList.length > 0 && ( -
- {metricInfoList.map(item => ( - { - if (onSelectSuggestion) { - onSelectSuggestion(value); - } - }} - /> - ))} -
+ {!isMetricCard && ( + )}
diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/style.less b/webapp/packages/chat-sdk/src/components/ChatItem/style.less index 842a035bd..68d68deb7 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatItem/style.less @@ -19,7 +19,6 @@ } &-content { - // flex: 1; width: calc(100% - 50px); } @@ -42,7 +41,7 @@ &-typing-bubble { width: fit-content; - padding: 8px 16px !important; + padding: 16px !important; } &-text-bubble { 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 85fd7ab30..3ef6320c1 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/index.tsx @@ -1,22 +1,38 @@ import { CHART_BLUE_COLOR, CHART_SECONDARY_COLOR, PREFIX_CLS } from '../../../common/constants'; -import { MsgDataType } from '../../../common/type'; +import { DrillDownDimensionType, MsgDataType } from '../../../common/type'; import { getChartLightenColor, getFormattedValue } from '../../../utils/utils'; import type { ECharts } from 'echarts'; import * as echarts from 'echarts'; import React, { useEffect, useRef, useState } from 'react'; import NoPermissionChart from '../NoPermissionChart'; +import DrillDownDimensions from '../../DrillDownDimensions'; +import { Spin } from 'antd'; +import FilterSection from '../FilterSection'; type Props = { data: MsgDataType; triggerResize?: boolean; + drillDownDimension?: DrillDownDimensionType; + loading: boolean; + onSelectDimension: (dimension?: DrillDownDimensionType) => void; onApplyAuth?: (domain: string) => void; }; -const BarChart: React.FC = ({ data, triggerResize, onApplyAuth }) => { +const BarChart: React.FC = ({ + data, + triggerResize, + drillDownDimension, + loading, + onSelectDimension, + onApplyAuth, +}) => { const chartRef = useRef(); const [instance, setInstance] = useState(); - const { queryColumns, queryResults, entityInfo } = data; + const { queryColumns, queryResults, entityInfo, chatContext, queryMode } = data; + + const { dateInfo } = chatContext || {}; + const categoryColumnName = queryColumns?.find(column => column.showType === 'CATEGORY')?.nameEn || ''; const metricColumn = queryColumns?.find(column => column.showType === 'NUMBER'); @@ -35,13 +51,13 @@ const BarChart: React.FC = ({ data, triggerResize, onApplyAuth }) => { ); const xData = data.map(item => item[categoryColumnName]); instanceObj.setOption({ - legend: { - left: 0, - top: 0, - icon: 'rect', - itemWidth: 15, - itemHeight: 5, - }, + // legend: { + // left: 0, + // top: 0, + // icon: 'rect', + // itemWidth: 15, + // itemHeight: 5, + // }, xAxis: { type: 'category', axisTick: { @@ -99,7 +115,7 @@ const BarChart: React.FC = ({ data, triggerResize, onApplyAuth }) => { left: '2%', right: '1%', bottom: '3%', - top: 50, + top: 20, containLabel: true, }, series: { @@ -150,7 +166,30 @@ const BarChart: React.FC = ({ data, triggerResize, onApplyAuth }) => { ); } - return
; + return ( +
+
{metricColumn?.name}
+ + {dateInfo && ( +
+ {dateInfo.startDate === dateInfo.endDate + ? dateInfo.startDate + : `${dateInfo.startDate} ~ ${dateInfo.endDate}`} +
+ )} + +
+ + {(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && ( + + )} +
+ ); }; export default BarChart; diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/style.less index 847651665..e0dfb3ef7 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Bar/style.less @@ -3,6 +3,20 @@ @bar-cls: ~'@{supersonic-chat-prefix}-bar'; .@{bar-cls} { - height: 270px; - margin-top: 16px; + + &-chart { + height: 260px; + margin-top: 16px; + } + + &-metric-name { + font-size: 15px; + font-weight: 500; + } + + &-date-range { + margin-top: 12px; + font-size: 13px; + color: var(--text-color-third); + } } \ No newline at end of file diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/FilterSection/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/FilterSection/index.tsx new file mode 100644 index 000000000..43160274d --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/FilterSection/index.tsx @@ -0,0 +1,38 @@ +import { PREFIX_CLS } from '../../../common/constants'; +import { ChatContextType } from '../../../common/type'; + +type Props = { + chatContext?: ChatContextType; +}; + +const FilterSection: React.FC = ({ chatContext }) => { + const prefixCls = `${PREFIX_CLS}-filter-section`; + + const { dimensionFilters } = chatContext || {}; + + const hasFilterSection = dimensionFilters && dimensionFilters.length > 0; + + return hasFilterSection ? ( +
+
筛选条件:
+
+ {dimensionFilters.map(filterItem => { + const filterValue = + typeof filterItem.value === 'string' ? [filterItem.value] : filterItem.value || []; + return ( +
+ {filterItem.name}: + {filterValue.join('、')} +
+ ); + })} +
+
+ ) : null; +}; + +export default FilterSection; diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/FilterSection/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/FilterSection/style.less new file mode 100644 index 000000000..c18d4606a --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/FilterSection/style.less @@ -0,0 +1,33 @@ +@import '../../../styles/index.less'; + +@filter-section-prefix-cls: ~'@{supersonic-chat-prefix}-filter-section'; + +.@{filter-section-prefix-cls} { + display: flex; + align-items: center; + color: var(--text-color-secondary); + font-weight: normal; + font-size: 13px; + + &-filter-values { + display: flex; + align-items: center; + column-gap: 6px; + } + + &-filter-item { + padding: 2px 12px; + color: var(--text-color-third); + background-color: #edf2f2; + border-radius: 13px; + max-width: 200px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + &-filter-value { + color: var(--text-color); + font-weight: 500; + } +} diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Message/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/Message/index.tsx index f398ca88b..0a76ee136 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Message/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Message/index.tsx @@ -65,7 +65,14 @@ const Message: React.FC = ({ return (
- {domainName &&
{domainName}
} +
+ {domainName &&
{domainName}
} + {position === 'left' && leftTitle && ( +
+ ({leftTitle}) +
+ )} +
= ({ e.stopPropagation(); }} > - {position === 'left' && title && ( -
- {leftTitle} -
- )} - {(entityInfoList.length > 0 || hasFilterSection) && ( + {entityInfoList.length > 0 && (
- {filterSection} + {/* {filterSection} */} {entityInfoList.length > 0 && (
{entityInfoList.slice(0, 4).map(dimension => { diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Message/style.less b/webapp/packages/chat-sdk/src/components/ChatMsg/Message/style.less index d2b121475..f7ffa03c5 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Message/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Message/style.less @@ -3,11 +3,28 @@ @msg-prefix-cls: ~'@{supersonic-chat-prefix}-message'; .@{msg-prefix-cls} { + &-title-bar { + display: flex; + align-items: baseline; + column-gap: 10px; + margin-bottom: 6px; + } + &-domain-name { color: var(--text-color); - margin-bottom: 2px; margin-left: 4px; font-weight: 500; + font-size: 15px; + } + + &-top-bar { + position: relative; + max-width: 80%; + color: var(--text-color-third); + font-size: 13px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } &-content { @@ -20,6 +37,7 @@ } &-bubble { + position: relative; box-sizing: border-box; min-width: 1px; max-width: 100%; @@ -30,18 +48,6 @@ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12); } - &-top-bar { - position: relative; - max-width: 100%; - padding: 4px 0 8px; - 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); - } - &-filter-section { display: flex; align-items: center; @@ -77,7 +83,7 @@ align-items: center; row-gap: 12px; flex-wrap: wrap; - margin-top: 20px; + margin-top: 4px; column-gap: 20px; color: var(--text-color-secondary); background: rgba(133, 156, 241, 0.1); diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/PeriodCompareItem.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/PeriodCompareItem.tsx new file mode 100644 index 000000000..fdc7baf0f --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/PeriodCompareItem.tsx @@ -0,0 +1,29 @@ +import classNames from 'classnames'; +import { PREFIX_CLS } from '../../../common/constants'; +import IconFont from '../../IconFont'; + +type Props = { + title: string; + value: string; +}; + +const PeriodCompareItem: React.FC = ({ title, value }) => { + const prefixCls = `${PREFIX_CLS}-metric-card`; + + const itemValueClass = classNames(`${prefixCls}-period-compare-item-value`, { + [`${prefixCls}-period-compare-item-value-up`]: !value.includes('-'), + [`${prefixCls}-period-compare-item-value-down`]: value.includes('-'), + }); + + return ( +
+
{title}
+
+ +
{value}
+
+
+ ); +}; + +export default PeriodCompareItem; 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 f26528f30..437747c9f 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/index.tsx @@ -1,36 +1,77 @@ import { PREFIX_CLS } from '../../../common/constants'; -import { getFormattedValue } from '../../../utils/utils'; +import { formatByThousandSeperator } from '../../../utils/utils'; import ApplyAuth from '../ApplyAuth'; -import { MsgDataType } from '../../../common/type'; +import { DrillDownDimensionType, MsgDataType } from '../../../common/type'; +import PeriodCompareItem from './PeriodCompareItem'; +import DrillDownDimensions from '../../DrillDownDimensions'; +import { Spin } from 'antd'; +import classNames from 'classnames'; type Props = { data: MsgDataType; + drillDownDimension?: DrillDownDimensionType; + loading: boolean; + onSelectDimension: (dimension?: DrillDownDimensionType) => void; onApplyAuth?: (domain: string) => void; }; -const MetricCard: React.FC = ({ data, onApplyAuth }) => { - const { queryColumns, queryResults, entityInfo } = data; +const MetricCard: React.FC = ({ + data, + drillDownDimension, + loading, + onSelectDimension, + onApplyAuth, +}) => { + const { queryMode, queryColumns, queryResults, entityInfo, aggregateInfo, chatContext } = data; + + const { metricInfos } = aggregateInfo || {}; + const { dateInfo } = chatContext || {}; + const { startDate, endDate } = dateInfo || {}; const indicatorColumn = queryColumns?.find(column => column.showType === 'NUMBER'); const indicatorColumnName = indicatorColumn?.nameEn || ''; const prefixCls = `${PREFIX_CLS}-metric-card`; + const indicatorClass = classNames(`${prefixCls}-indicator`, { + [`${prefixCls}-indicator-period-compare`]: metricInfos?.length > 0, + }); + return (
-
- {/*
- {startTime === endTime ? startTime : `${startTime} ~ ${endTime}`} -
*/} - {indicatorColumn && !indicatorColumn?.authorized ? ( - - ) : ( -
- {getFormattedValue(queryResults?.[0]?.[indicatorColumnName])} +
{indicatorColumn?.name}
+ +
+
+ {startDate === endDate ? startDate : `${startDate} ~ ${endDate}`}
- )} - {/*
{query}
*/} -
+ {indicatorColumn && !indicatorColumn?.authorized ? ( + + ) : ( +
+ {formatByThousandSeperator(queryResults?.[0]?.[indicatorColumnName])} +
+ )} + {metricInfos?.length > 0 && ( +
+ {Object.keys(metricInfos[0].statistics).map((key: any) => ( + + ))} +
+ )} +
+ + {(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && ( +
+ +
+ )}
); }; 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 a769e7e45..26685e1af 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricCard/style.less @@ -3,34 +3,78 @@ @metric-card-prefix-cls: ~'@{supersonic-chat-prefix}-metric-card'; .@{metric-card-prefix-cls} { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; width: 100%; - height: 150px; + height: 130px; row-gap: 4px; + &-indicator-name { + font-size: 14px; + color: var(--text-color); + font-weight: 500; + margin-top: 2px; + } + &-indicator { display: flex; flex-direction: column; + align-items: center; + justify-content: flex-start; + } + + &-indicator-period-compare { align-items: flex-start; - justify-content: center; } &-date-range { color: var(--text-color-fourth); - font-size: 14px; + font-size: 12px; + margin-top: 8px; } &-indicator-value { color: var(--text-color); - font-weight: 600; - font-size: 30px; + font-weight: 700; + font-size: 40px; + color: var(--chat-blue); } - &-indicator-name { + &-period-compare { + width: 100%; + display: flex; + align-items: center; + column-gap: 40px; + font-size: 13px; + overflow-x: auto; + } + + &-period-compare-item { + display: flex; + align-items: center; + column-gap: 10px; + } + + &-period-compare-item-title { color: var(--text-color-fourth); - font-size: 14px; + } + + &-period-compare-item-value { + display: flex; + align-items: center; + column-gap: 4px; + font-weight: 500; + } + + &-period-compare-item-value-up { + color: rgb(252, 103, 114); + } + + &-period-compare-item-value-down { + color: rgb(45, 202, 147); + } + + &-drill-down-dimensions { + position: absolute; + bottom: -38px; + left: 0; } } \ 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 new file mode 100644 index 000000000..c67f11a8c --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/MetricInfo.tsx @@ -0,0 +1,34 @@ +import { PREFIX_CLS } from '../../../common/constants'; +import { formatByThousandSeperator } from '../../../utils/utils'; +import { AggregateInfoType } from '../../../common/type'; +import PeriodCompareItem from '../MetricCard/PeriodCompareItem'; + +type Props = { + aggregateInfo: AggregateInfoType; +}; + +const MetricInfo: React.FC = ({ aggregateInfo }) => { + const { metricInfos } = aggregateInfo || {}; + const metricInfo = metricInfos?.[0] || {}; + const { date, value, statistics } = metricInfo || {}; + + const prefixCls = `${PREFIX_CLS}-metric-info`; + + return ( +
+
+
{date}
+
{formatByThousandSeperator(value)}
+ {metricInfos?.length > 0 && ( +
+ {Object.keys(statistics).map((key: any) => ( + + ))} +
+ )} +
+
+ ); +}; + +export default MetricInfo; 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 268bacbb3..0389f3ecd 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/index.tsx @@ -1,24 +1,27 @@ import { useEffect, useState } from 'react'; import { CLS_PREFIX, DATE_TYPES } from '../../../common/constants'; -import { ColumnType, FieldType, MsgDataType } from '../../../common/type'; +import { ColumnType, DrillDownDimensionType, 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 DrillDownDimensions from '../../DrillDownDimensions'; +import MetricInfo from './MetricInfo'; +import FilterSection from '../FilterSection'; +import moment from 'moment'; type Props = { data: MsgDataType; triggerResize?: boolean; onApplyAuth?: (domain: string) => void; - onCheckMetricInfo?: (data: any) => void; }; -const MetricTrend: React.FC = ({ data, triggerResize, onApplyAuth, onCheckMetricInfo }) => { - const { queryColumns, queryResults, entityInfo, chatContext } = data; +const MetricTrend: React.FC = ({ data, triggerResize, onApplyAuth }) => { + const { queryColumns, queryResults, entityInfo, chatContext, queryMode, aggregateInfo } = data; - const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES[0]; + const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY; const initialDateOption = dateOptions.find( (option: any) => option.value === chatContext?.dateInfo?.unit )?.value; @@ -29,6 +32,7 @@ const MetricTrend: React.FC = ({ data, triggerResize, onApplyAuth, onChec const [activeMetricField, setActiveMetricField] = useState(chatContext.metrics?.[0]); const [dataSource, setDataSource] = useState(queryResults); const [currentDateOption, setCurrentDateOption] = useState(initialDateOption); + const [drillDownDimension, setDrillDownDimension] = useState(); const [loading, setLoading] = useState(false); const dateField: any = columns.find( @@ -57,9 +61,21 @@ const MetricTrend: React.FC = ({ data, triggerResize, onApplyAuth, onChec const selectDateOption = (dateOption: number) => { setCurrentDateOption(dateOption); + const endDate = moment().subtract(1, 'days').format('YYYY-MM-DD'); + const startDate = moment(endDate) + .subtract(dateOption - 1, 'days') + .format('YYYY-MM-DD'); onLoadData({ metrics: [activeMetricField], - dateInfo: { ...chatContext?.dateInfo, unit: dateOption }, + dimensions: drillDownDimension + ? [...(chatContext.dimensions || []), drillDownDimension] + : undefined, + dateInfo: { + ...chatContext?.dateInfo, + startDate, + endDate, + unit: dateOption, + }, }); }; @@ -67,10 +83,23 @@ const MetricTrend: React.FC = ({ data, triggerResize, onApplyAuth, onChec setActiveMetricField(metricField); onLoadData({ dateInfo: { ...chatContext.dateInfo, unit: currentDateOption || chatContext.dateInfo.unit }, + dimensions: drillDownDimension + ? [...(chatContext.dimensions || []), drillDownDimension] + : undefined, metrics: [metricField], }); }; + const onSelectDimension = (dimension?: DrillDownDimensionType) => { + setDrillDownDimension(dimension); + onLoadData({ + dateInfo: { ...chatContext.dateInfo, unit: currentDateOption || chatContext.dateInfo.unit }, + metrics: [activeMetricField], + dimensions: + dimension === undefined ? undefined : [...(chatContext.dimensions || []), dimension], + }); + }; + if (!currentMetricField) { return null; } @@ -80,6 +109,33 @@ const MetricTrend: React.FC = ({ data, triggerResize, onApplyAuth, onChec return (
+ {chatContext.metrics.length > 0 && ( +
+ {chatContext.metrics.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} +
+ ); + })} +
+ )} + {aggregateInfo?.metricInfos?.length > 0 && } +
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => { const dateOptionClass = classNames(`${prefixCls}-date-option`, { @@ -107,41 +163,10 @@ const MetricTrend: React.FC = ({ data, triggerResize, onApplyAuth, onChec ); })}
- {chatContext.metrics.length > 0 && ( -
- {chatContext.metrics.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} - {/* */} -
- ); - })} -
- )} - {dataSource?.length === 1 ? ( - - ) : ( - + + {dataSource?.length === 1 ? ( +
+ ) : ( = ({ data, triggerResize, onApplyAuth, onChec triggerResize={triggerResize} onApplyAuth={onApplyAuth} /> - + )} + + {(queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && ( + )} 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 1d74a514a..e76bbd3ed 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/MetricTrend/style.less @@ -2,12 +2,14 @@ @metric-trend-prefix-cls: ~'@{supersonic-chat-prefix}-metric-trend'; +@metric-info-prefix-cls: ~'@{supersonic-chat-prefix}-metric-info'; + .@{metric-trend-prefix-cls} { display: flex; flex-direction: column; align-items: center; justify-content: center; - margin-top: 16px; + margin-top: 4px; width: 100%; row-gap: 4px; @@ -35,14 +37,15 @@ } &-flow-trend-chart { - height: 270px; + margin-top: 4px; + height: 230px; } &-charts { display: flex; flex-direction: column; width: 100%; - row-gap: 16px; + row-gap: 12px; } &-metric-fields { @@ -50,6 +53,8 @@ flex-wrap: wrap; align-items: center; row-gap: 12px; + color: var(--text-color); + font-size: 15px; } &-metric-field { @@ -85,10 +90,11 @@ padding-left: 0; font-weight: 500; cursor: default; - color: var(--text-color-secondary); + font-size: 15px; + color: var(--text-color); &:hover { - color: var(--text-color-secondary); + color: var(--text-color); } } @@ -133,3 +139,36 @@ } } +.@{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; + } + + &-indicator-value { + color: var(--text-color); + font-weight: 500; + font-size: 36px; + line-height: 40px; + margin-top: 2px; + color: var(--text-color-secondary); + } + + &-period-compare { + width: 100%; + 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/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx index 920ebd64a..4ba05a9c3 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/index.tsx @@ -4,7 +4,9 @@ import Message from './Message'; import MetricCard from './MetricCard'; import MetricTrend from './MetricTrend'; import Table from './Table'; -import { MsgDataType } from '../../common/type'; +import { ColumnType, DrillDownDimensionType, MsgDataType } from '../../common/type'; +import { useState } from 'react'; +import { queryData } from '../../service'; type Props = { question: string; @@ -12,7 +14,6 @@ type Props = { data: MsgDataType; isMobileMode?: boolean; triggerResize?: boolean; - onCheckMetricInfo?: (data: any) => void; }; const ChatMsg: React.FC = ({ @@ -21,48 +22,95 @@ const ChatMsg: React.FC = ({ data, isMobileMode, triggerResize, - onCheckMetricInfo, }) => { const { queryColumns, queryResults, chatContext, entityInfo, queryMode } = data; + const [columns, setColumns] = useState(queryColumns); + const [dataSource, setDataSource] = useState(queryResults); + + const [drillDownDimension, setDrillDownDimension] = useState(); + const [loading, setLoading] = useState(false); + if (!queryColumns || !queryResults) { return null; } - const singleData = queryResults.length === 1; - const dateField = queryColumns.find(item => item.showType === 'DATE' || item.type === 'DATE'); - const categoryField = queryColumns.filter(item => item.showType === 'CATEGORY'); - const metricFields = queryColumns.filter(item => item.showType === 'NUMBER'); + 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'); + + const isMetricCard = + (queryMode === 'METRIC_DOMAIN' || queryMode === 'METRIC_FILTER') && singleData; + + const onLoadData = async (value: any) => { + setLoading(true); + const { data } = await queryData({ + ...chatContext, + ...value, + }); + setLoading(false); + if (data.code === 200) { + setColumns(data.data?.queryColumns || []); + setDataSource(data.data?.queryResults || []); + } + }; + + const onSelectDimension = (dimension?: DrillDownDimensionType) => { + setDrillDownDimension(dimension); + onLoadData({ + dimensions: + dimension === undefined ? undefined : [...(chatContext.dimensions || []), dimension], + }); + }; const getMsgContent = () => { + if (isMetricCard) { + return ( + + ); + } if ( categoryField.length > 1 || queryMode === 'ENTITY_DETAIL' || queryMode === 'ENTITY_DIMENSION' || (categoryField.length === 1 && metricFields.length === 0) ) { - return
; + return
; } if (dateField && metricFields.length > 0) { - return ( - - ); + if (!dataSource.every(item => item[dateField.nameEn] === dataSource[0][dateField.nameEn])) { + return ( + + ); + } } - if (singleData) { - return ; - } - return ; + return ( + + ); }; let width = '100%'; - if (categoryField.length > 1 && !isMobile && !isMobileMode) { - if (queryColumns.length === 1) { + if (isMetricCard) { + width = '370px'; + } else if (categoryField.length > 1 && !isMobile && !isMobileMode) { + if (columns.length === 1) { width = '600px'; - } else if (queryColumns.length === 2) { + } else if (columns.length === 2) { width = '1000px'; } } diff --git a/webapp/packages/chat-sdk/src/components/DrillDownDimensions/index.tsx b/webapp/packages/chat-sdk/src/components/DrillDownDimensions/index.tsx new file mode 100644 index 000000000..0fdd7714b --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/DrillDownDimensions/index.tsx @@ -0,0 +1,122 @@ +import { useEffect, useState } from 'react'; +import { CLS_PREFIX } from '../../common/constants'; +import { DrillDownDimensionType, FilterItemType } from '../../common/type'; +import { queryDrillDownDimensions } from '../../service'; +import { Dropdown } from 'antd'; +import { DownOutlined } from '@ant-design/icons'; +import classNames from 'classnames'; + +type Props = { + domainId: number; + drillDownDimension?: DrillDownDimensionType; + isMetricCard?: boolean; + dimensionFilters?: FilterItemType[]; + onSelectDimension: (dimension?: DrillDownDimensionType) => void; +}; + +const DEFAULT_DIMENSION_COUNT = 5; +const MAX_DIMENSION_COUNT = 20; + +const DrillDownDimensions: React.FC = ({ + domainId, + drillDownDimension, + isMetricCard, + dimensionFilters, + onSelectDimension, +}) => { + const [dimensions, setDimensions] = useState([]); + + const prefixCls = `${CLS_PREFIX}-drill-down-dimensions`; + + const initData = async () => { + const res = await queryDrillDownDimensions(domainId); + setDimensions( + res.data.data.dimensions + .filter(dimension => !dimensionFilters?.some(filter => filter.name === dimension.name)) + .slice(0, MAX_DIMENSION_COUNT) + ); + }; + + useEffect(() => { + initData(); + }, []); + + const cancelDrillDown = () => { + onSelectDimension(undefined); + }; + + const defaultDimensions = dimensions.slice(0, DEFAULT_DIMENSION_COUNT); + + const drillDownDimensionsSectionClass = classNames(`${prefixCls}-section`, { + [`${prefixCls}-metric-card`]: isMetricCard, + }); + + return ( +
+
+
快速维度下钻:
+
+ {defaultDimensions.map((dimension, index) => { + const itemNameClass = classNames(`${prefixCls}-content-item-name`, { + [`${prefixCls}-content-item-active`]: drillDownDimension?.id === dimension.id, + }); + return ( +
+ { + onSelectDimension( + drillDownDimension?.id === dimension.id ? undefined : dimension + ); + }} + > + {dimension.name} + + {index !== defaultDimensions.length - 1 && } +
+ ); + })} + {dimensions.length > DEFAULT_DIMENSION_COUNT && ( +
+ + { + const itemNameClass = classNames({ + [`${prefixCls}-menu-item-active`]: drillDownDimension?.id === dimension.id, + }); + return { + label: ( + { + onSelectDimension(dimension); + }} + > + {dimension.name} + + ), + key: dimension.id, + }; + }), + }} + > + + 更多 + + + +
+ )} + {drillDownDimension && ( +
+ 取消下钻 +
+ )} +
+
+
+ ); +}; + +export default DrillDownDimensions; diff --git a/webapp/packages/chat-sdk/src/components/DrillDownDimensions/style.less b/webapp/packages/chat-sdk/src/components/DrillDownDimensions/style.less new file mode 100644 index 000000000..6d5c46e41 --- /dev/null +++ b/webapp/packages/chat-sdk/src/components/DrillDownDimensions/style.less @@ -0,0 +1,74 @@ +@import '../../styles/index.less'; + +@drill-down-dimensions-prefix-cls: ~'@{supersonic-chat-prefix}-drill-down-dimensions'; + +.@{drill-down-dimensions-prefix-cls} { + display: flex; + flex-direction: column; + + &-section { + width: 100%; + display: flex; + align-items: center; + flex-wrap: wrap; + column-gap: 6px; + margin-top: 6px; + margin-bottom: 6px; + } + + &-metric-card { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12); + border-radius: 8px; + background-color: #fff; + width: fit-content; + padding: 2px 4px; + font-size: 12px; + } + + &-title { + color: var(--text-color-third); + } + + &-content { + display: flex; + align-items: center; + } + + &-content-item-name { + color: var(--chat-blue); + font-weight: 500; + border-bottom: 1px solid var(--chat-blue); + padding: 1px; + cursor: pointer; + } + + &-content-item-active { + color: #fff; + border-bottom: none; + background-color: var(--chat-blue); + border-radius: 2px; + } + + &-menu-item-active { + color: var(--chat-blue); + } + + &-down-arrow { + color: var(--chat-blue); + } + + &-cancel-drill-down { + margin-left: 20px; + color: var(--text-color-third); + cursor: pointer; + padding: 0 4px; + border: 1px solid var(--text-color-third); + border-radius: 4px; + font-size: 12px; + + &:hover { + color: var(--chat-blue); + border-color: var(--chat-blue); + } + } +} \ No newline at end of file diff --git a/webapp/packages/chat-sdk/src/components/IconFont/index.tsx b/webapp/packages/chat-sdk/src/components/IconFont/index.tsx index 709314cc9..794fc52e6 100644 --- a/webapp/packages/chat-sdk/src/components/IconFont/index.tsx +++ b/webapp/packages/chat-sdk/src/components/IconFont/index.tsx @@ -1,7 +1,7 @@ import { createFromIconfontCN } from '@ant-design/icons'; const IconFont = createFromIconfontCN({ - scriptUrl: '//at.alicdn.com/t/c/font_4120566_hsbqfckf8tl.js', + scriptUrl: '//at.alicdn.com/t/c/font_4120566_imm6kslj5s.js', }); export default IconFont; diff --git a/webapp/packages/chat-sdk/src/components/SemanticDetail/BasicInfoSection.tsx b/webapp/packages/chat-sdk/src/components/SemanticDetail/BasicInfoSection.tsx deleted file mode 100644 index 31af3e0f2..000000000 --- a/webapp/packages/chat-sdk/src/components/SemanticDetail/BasicInfoSection.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { CLS_PREFIX } from '../../common/constants'; -import { Row, Col } from 'antd'; - -type Props = { - description: string; - basicInfoList: any[]; -}; - -const BasicInfoSection: React.FC = ({ description = '', basicInfoList }) => { - const prefixCls = `${CLS_PREFIX}-semantic-detail`; - - return ( - <> -
-
- {basicInfoList.map(item => { - return ( -
-
{item.name}:
-
{item.value}
-
- ); - })} -
-
- {description && ( - <> - -
-
口径:
- - -
{description}
- - - - )} - - ); -}; - -export default BasicInfoSection; diff --git a/webapp/packages/chat-sdk/src/components/SemanticDetail/DimensionSection.tsx b/webapp/packages/chat-sdk/src/components/SemanticDetail/DimensionSection.tsx deleted file mode 100644 index ef9a5f19a..000000000 --- a/webapp/packages/chat-sdk/src/components/SemanticDetail/DimensionSection.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { message, Row, Col } from 'antd'; -import { isMobile } from '../../utils/utils'; -import { ReloadOutlined } from '@ant-design/icons'; -import React, { useEffect, useState } from 'react'; -import { getRelatedDimensionFromStatInfo } from '../../service'; -import { CLS_PREFIX } from '../../common/constants'; - -type Props = { - classId?: number; - uniqueId: string | number; - onSelect?: (value: string) => void; -}; -const PAGE_SIZE = isMobile ? 3 : 10; - -const DimensionSection: React.FC = ({ classId, uniqueId, onSelect }) => { - const [dimensions, setDimensions] = useState([]); - const [dimensionIndex, setDimensionIndex] = useState(0); - - const prefixCls = `${CLS_PREFIX}-semantic-detail`; - - const queryDimensionList = async () => { - const { data: resData } = await getRelatedDimensionFromStatInfo({ - classId, - uniqueId, - }); - const { code, data, msg } = resData; - if (code === '0') { - setDimensions( - data.map(item => { - return item.name; - }) - ); - } else { - message.error(msg); - } - }; - - useEffect(() => { - queryDimensionList(); - }, []); - - const reloadDimensionCmds = () => { - const dimensionPageCount = Math.ceil(dimensions.length / PAGE_SIZE); - setDimensionIndex((dimensionIndex + 1) % dimensionPageCount); - }; - - const dimensionList = dimensions.slice( - dimensionIndex * PAGE_SIZE, - (dimensionIndex + 1) * PAGE_SIZE - ); - - return ( - <> - {dimensionList.length > 0 && ( -
- -
-
常用维度:
- - -
- {dimensionList.map((dimension, index) => { - return ( - <> - { - onSelect?.(dimension); - }} - > - {dimension} - - {index < dimensionList.length - 1 && '、'} - - ); - })} -
- - -
{ - reloadDimensionCmds(); - }} - > - - {!isMobile &&
换一批
} -
- - - - )} - - ); -}; - -export default DimensionSection; diff --git a/webapp/packages/chat-sdk/src/components/SemanticDetail/RecommendSection.tsx b/webapp/packages/chat-sdk/src/components/SemanticDetail/RecommendSection.tsx deleted file mode 100644 index f508d4521..000000000 --- a/webapp/packages/chat-sdk/src/components/SemanticDetail/RecommendSection.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { useEffect, useState } from 'react'; -import { getMetricQueryInfo } from '../../service'; -import { message, Row, Col } from 'antd'; -import { CLS_PREFIX } from '../../common/constants'; - -type Props = { - classId: number; - metricName: string; - onSelect?: (value: string) => void; -}; - -const RecommendQuestions: React.FC = ({ classId, metricName, onSelect }) => { - const [moreMode, setMoreMode] = useState(false); - const [questionData, setQuestionData] = useState([]); - - const prefixCls = `${CLS_PREFIX}-semantic-detail`; - - const queryMetricQueryInfo = async () => { - const { data: resData } = await getMetricQueryInfo({ - classId, - metricName, - }); - const { code, data, msg } = resData; - if (code === '0') { - setQuestionData(data); - } else { - message.error(msg); - } - }; - - useEffect(() => { - queryMetricQueryInfo(); - }, []); - - return ( -
-
- -
-
大家都在问:
- - - {!moreMode && ( -
- {questionData.slice(0, 5).map((item, index) => { - const { question } = item; - return ( - <> - {index !== 0 && '、'} - { - onSelect?.(question); - }} - > - - - - ); - })} -
- )} - - - {!moreMode ? ( - { - setMoreMode(true); - }} - className={`${prefixCls}-more`} - > - 更多 - - ) : ( - { - setMoreMode(false); - }} - > - 收起 - - )} - - - - {moreMode && ( -
-
- {questionData.map(item => { - const { question } = item; - return ( -
{ - onSelect?.(question); - }} - > - -
- ); - })} -
-
- )} - - ); -}; - -export default RecommendQuestions; diff --git a/webapp/packages/chat-sdk/src/components/SemanticDetail/index.tsx b/webapp/packages/chat-sdk/src/components/SemanticDetail/index.tsx deleted file mode 100644 index 4155bf24b..000000000 --- a/webapp/packages/chat-sdk/src/components/SemanticDetail/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import Message from '../ChatMsg/Message'; -import { Space, Row, Col, Divider } from 'antd'; -import BasicInfoSection from './BasicInfoSection'; -import DimensionSection from './DimensionSection'; -import RecommendSection from './RecommendSection'; -import SemanticTypeTag from '../ChatMsg/SemanticInfoPopover/SemanticTypeTag'; -import { CLS_PREFIX } from '../../common/constants'; - -type Props = { - dataSource?: any; - onDimensionSelect?: (value: any) => void; -}; - -const SemanticDetail: React.FC = ({ dataSource, onDimensionSelect }) => { - const { name, nameEn, createdBy, description, className, classId, semanticInfoType } = dataSource; - - const semanticDetailCls = `${CLS_PREFIX}-semantic-detail`; - - return ( - -
-
- -
- - {`指标详情: ${name}`} - - - - - - - - - - - - - - - ); -}; - -export default SemanticDetail; diff --git a/webapp/packages/chat-sdk/src/components/SemanticDetail/style.less b/webapp/packages/chat-sdk/src/components/SemanticDetail/style.less deleted file mode 100644 index 6251fc898..000000000 --- a/webapp/packages/chat-sdk/src/components/SemanticDetail/style.less +++ /dev/null @@ -1,129 +0,0 @@ -@import '../../styles/variables.less'; - -@semantic-detail-cls: ~'@{supersonic-chat-prefix}-semantic-detail'; - -.@{semantic-detail-cls} { - &-info-bar { - display: flex; - flex-wrap: wrap; - align-items: center; - margin: 20px 0; - column-gap: 20px; - } - - &-description { - font-size: 13px; - color: var(--text-color-fourth); - } - - &-main-entity-info { - display: flex; - flex-wrap: wrap; - align-items: center; - font-size: 13px; - column-gap: 20px; - } - - &-info-item { - display: flex; - align-items: center; - } - - &-info-name { - color: var(--text-color-fourth); - } - - &-info-value { - color: var(--text-color-secondary); - } - - &-title { - font-size: 16px; - margin-left: 15px; - line-height: 30px; - &::before { - display: block; - position: absolute; - content: ""; - left: 0; - top: 6px; - height: 15px; - width: 3px; - font-size: 0; - background: #0e73ff; - border-radius: 2px; - border: 1px solid #0e73ff; - } - } - - &-label { - font-size: 14px; - color: var(--text-color-fourth); - } - - &-section-item { - cursor: pointer; - - &:hover { - color: var(--chat-blue); - } - } - - &-reload { - display: flex; - align-items: center; - color: var(--text-color-fourth); - font-size: 12px; - column-gap: 4px; - cursor: pointer; - position: relative; - top: 3px; - &:hover { - color: var(--chat-blue); - } - } - - &-reload-icon { - font-size: 10px; - position: relative; - } - - &-header { - font-size: 14px; - color: var(--text-color-fourth); - margin-bottom: 5px; - } - - &-more { - cursor: pointer; - font-size: 12px; - &:hover { - color: var(--chat-blue); - } - } - - &-recommend-questions-content { - height: 300px; - overflow: auto; - } - - &-question { - cursor: pointer; - color: rgba(0, 0, 0, 0.87); - &:hover { - color: var(--chat-blue); - } - } - - &-content-col { - min-width: 300px; - overflow-y: hidden; - height: 32px; - overflow-x: scroll; - } - - &-content-col-box{ - width: max-content; - } -} - diff --git a/webapp/packages/chat-sdk/src/demo/Chat.tsx b/webapp/packages/chat-sdk/src/demo/Chat.tsx index 981993f46..62d2d271b 100644 --- a/webapp/packages/chat-sdk/src/demo/Chat.tsx +++ b/webapp/packages/chat-sdk/src/demo/Chat.tsx @@ -54,7 +54,7 @@ const Chat = () => {
>(`${prefix}/chat/recommend/metric/${domainId}`); +} diff --git a/webapp/packages/chat-sdk/src/styles/index.less b/webapp/packages/chat-sdk/src/styles/index.less index c246be1eb..4d39ba9e4 100644 --- a/webapp/packages/chat-sdk/src/styles/index.less +++ b/webapp/packages/chat-sdk/src/styles/index.less @@ -20,7 +20,7 @@ @import "../components/ChatMsg/SemanticInfoPopover/style.less"; -@import "../components/SemanticDetail/style.less"; +@import "../components/ChatMsg/FilterSection/style.less"; @import '../components/ChatItem/style.less'; @@ -28,3 +28,5 @@ @import "../components/Suggestion/style.less"; +@import "../components/DrillDownDimensions/style.less"; + diff --git a/webapp/packages/supersonic-fe/package.json b/webapp/packages/supersonic-fe/package.json index 2136a0009..5207b97b9 100644 --- a/webapp/packages/supersonic-fe/package.json +++ b/webapp/packages/supersonic-fe/package.json @@ -65,7 +65,6 @@ "@antv/layout": "^0.3.20", "@antv/xflow": "^1.0.55", "@babel/runtime": "^7.22.5", - "supersonic-chat-sdk": "^0.0.0", "@types/numeral": "^2.0.2", "@types/react-draft-wysiwyg": "^1.13.2", "@types/react-syntax-highlighter": "^13.5.0", @@ -97,6 +96,7 @@ "react-split-pane": "^2.0.3", "react-syntax-highlighter": "^15.4.3", "sql-formatter": "^2.3.3", + "supersonic-chat-sdk": "^0.0.0", "umi": "^3.2.14", "umi-request": "^1.0.8" }, diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/ChatFooter/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/ChatFooter/index.tsx index 67f9b991c..697b10789 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/ChatFooter/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/ChatFooter/index.tsx @@ -10,6 +10,7 @@ import { SemanticTypeEnum, SEMANTIC_TYPE_MAP } from '../constants'; import styles from './style.less'; import { PLACE_HOLDER } from '../constants'; import { DomainType } from '../type'; +import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; type Props = { inputMsg: string; @@ -17,6 +18,8 @@ type Props = { currentDomain?: DomainType; domains: DomainType[]; isMobileMode?: boolean; + collapsed: boolean; + onToggleCollapseBtn: () => void; onInputMsgChange: (value: string) => void; onSendMsg: (msg: string, domainId?: number) => void; onAddConversation: () => void; @@ -41,6 +44,8 @@ const ChatFooter: ForwardRefRenderFunction = ( currentDomain, domains, isMobileMode, + collapsed, + onToggleCollapseBtn, onInputMsgChange, onSendMsg, onAddConversation, @@ -239,6 +244,9 @@ const ChatFooter: ForwardRefRenderFunction = ( return (
+
+ {collapsed ? : } +
void; +}; + +const Conversation: ForwardRefRenderFunction = ( + { currentConversation, collapsed, onSelectConversation }, + ref, +) => { + const location = useLocation(); + const { q, cid, domainId, entityId } = (location as any).query; + const [conversations, setConversations] = useState([]); + const [editModalVisible, setEditModalVisible] = useState(false); + const [editConversation, setEditConversation] = useState(); + const [searchValue, setSearchValue] = useState(''); + + useImperativeHandle(ref, () => ({ + updateData, + onAddConversation, + })); + + const updateData = async () => { + const { data } = await getAllConversations(); + const conversationList = data || []; + setConversations(conversationList); + return conversationList; + }; + + const initData = async () => { + const data = await updateData(); + if (data.length > 0) { + const chatId = localStorage.getItem('CONVERSATION_ID') || cid; + if (chatId) { + const conversation = data.find((item: any) => item.chatId === +chatId); + if (conversation) { + onSelectConversation(conversation); + } else { + onSelectConversation(data[0]); + } + } else { + onSelectConversation(data[0]); + } + } else { + onAddConversation(); + } + }; + + useEffect(() => { + if (q && cid === undefined && window.location.href.includes('/workbench/chat')) { + onAddConversation({ name: q, domainId: domainId ? +domainId : undefined, entityId }); + } else { + initData(); + } + }, [q]); + + const addConversation = async (name?: string) => { + await saveConversation(name || DEFAULT_CONVERSATION_NAME); + return updateData(); + }; + + const onDeleteConversation = async (id: number) => { + await deleteConversation(id); + initData(); + }; + + const onAddConversation = async ({ + name, + domainId, + entityId, + type, + }: { + name?: string; + domainId?: number; + entityId?: string; + type?: string; + } = {}) => { + const data = await addConversation(name); + onSelectConversation(data[0], type || name, domainId, entityId); + }; + + const onOperate = (key: string, conversation: ConversationDetailType) => { + if (key === 'editName') { + setEditConversation(conversation); + setEditModalVisible(true); + } else if (key === 'delete') { + onDeleteConversation(conversation.chatId); + } + }; + + const conversationClass = classNames(styles.conversation, { + [styles.collapsed]: collapsed, + }); + + const convertTime = (date: string) => { + moment.locale('zh-cn'); + const now = moment(); + const inputDate = moment(date); + const diffMinutes = now.diff(inputDate, 'minutes'); + if (diffMinutes < 1) { + return '刚刚'; + } else if (inputDate.isSame(now, 'day')) { + return inputDate.format('HH:mm'); + } else if (inputDate.isSame(now.subtract(1, 'day'), 'day')) { + return '昨天'; + } + return inputDate.format('MM/DD'); + }; + + const onSearchValueChange = (e: React.ChangeEvent) => { + setSearchValue(e.target.value); + }; + + return ( +
+
+
+ } + className={styles.searchTask} + value={searchValue} + onChange={onSearchValueChange} + allowClear + /> +
+
+ {conversations + .filter( + (conversation) => + searchValue === '' || + conversation.chatName.toLowerCase().includes(searchValue.toLowerCase()), + ) + .map((item) => { + const conversationItemClass = classNames(styles.conversationItem, { + [styles.activeConversationItem]: currentConversation?.chatId === item.chatId, + }); + return ( + { + onOperate(key, item); + }} + /> + } + trigger={['contextMenu']} + > +
{ + onSelectConversation(item); + }} + > + +
+
+
{item.chatName}
+
+ {convertTime(item.lastTime || '')} +
+
+
{item.lastQuestion}
+
+
+
+ ); + })} +
+
+ { + setEditModalVisible(false); + }} + onFinish={() => { + setEditModalVisible(false); + updateData(); + }} + /> +
+ ); +}; + +export default forwardRef(Conversation); diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/Conversation/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/Conversation/style.less new file mode 100644 index 000000000..ff098750a --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/Chat/Conversation/style.less @@ -0,0 +1,149 @@ +.conversation { + position: relative; + width: 260px; + height: 100vh !important; + background-color: #fff; + border-right: 1px solid var(--border-color-base); + + .leftSection { + width: 100%; + height: 100%; + + .searchConversation { + display: flex; + align-items: center; + padding: 12px 9px 10px; + + .searchIcon { + color: #999 !important; + } + + .searchTask { + font-size: 13px; + background-color: #f5f5f5; + border: 0; + border-radius: 4px; + box-shadow: none !important; + + :global { + .ant-input { + font-size: 13px !important; + background-color: transparent !important; + } + } + } + } + + .conversationList { + height: calc(100vh - 50px); + padding: 2px 8px 0; + overflow-y: auto; + .conversationItem { + display: flex; + align-items: center; + margin-top: 2px; + padding: 6px 8px; + border-radius: 8px; + cursor: pointer; + + .conversationIcon { + margin-right: 10px; + color: var(--text-color-fourth); + font-size: 20px; + } + + .conversationContent { + width: 100%; + + .topTitleBar { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + + .conversationName { + width: 150px; + margin-right: 2px; + overflow: hidden; + color: var(--text-color); + font-size: 14px; + white-space: nowrap; + text-overflow: ellipsis; + } + + .conversationTime { + color: var(--text-color-six); + font-size: 12px; + } + } + + .subTitle { + width: 180px; + overflow: hidden; + color: var(--text-color-six); + font-size: 12px; + white-space: nowrap; + text-overflow: ellipsis; + } + } + + &.activeConversationItem { + background-color: var(--light-blue-background); + } + + &:hover { + background-color: var(--light-background); + } + } + } + + .operateSection { + margin-top: 20px; + padding-left: 15px; + } + + .operateItem { + display: flex; + align-items: center; + padding: 10px 0; + cursor: pointer; + + .operateIcon { + margin-right: 10px; + color: var(--text-color-fourth); + font-size: 20px; + } + + .operateLabel { + color: var(--text-color-third); + font-size: 14px; + } + + &:hover { + .operateLabel { + color: var(--chat-blue); + } + } + } + } + + &.collapsed { + width: 0; + padding: 0; + border-right: 0; + + .leftSection { + .searchConversation { + display: none; + } + + .conversationList { + display: none; + } + + .operateSection { + display: none; + } + } + } +} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/MessageContainer.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/MessageContainer.tsx index a9dee6df9..86f130eb6 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/MessageContainer.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/MessageContainer.tsx @@ -4,33 +4,30 @@ import { isEqual } from 'lodash'; import { ChatItem } from 'supersonic-chat-sdk'; import type { MsgDataType } from 'supersonic-chat-sdk'; import { MessageItem, MessageTypeEnum } from './type'; -import classNames from 'classnames'; -import { Skeleton } from 'antd'; import styles from './style.less'; +import RecommendQuestions from './components/RecommendQuestions'; type Props = { id: string; chatId: number; messageList: MessageItem[]; - miniProgramLoading: boolean; isMobileMode?: boolean; + conversationCollapsed: boolean; onClickMessageContainer: () => void; onMsgDataLoaded: (data: MsgDataType, questionId: string | number) => void; onSelectSuggestion: (value: string) => void; onCheckMore: (data: MsgDataType) => void; - onUpdateMessageScroll: () => void; }; const MessageContainer: React.FC = ({ id, chatId, messageList, - miniProgramLoading, isMobileMode, + conversationCollapsed, onClickMessageContainer, onMsgDataLoaded, onSelectSuggestion, - onUpdateMessageScroll, }) => { const [triggerResize, setTriggerResize] = useState(false); @@ -41,6 +38,10 @@ const MessageContainer: React.FC = ({ }, 0); }, []); + useEffect(() => { + onResize(); + }, [conversationCollapsed]); + useEffect(() => { window.addEventListener('resize', onResize); return () => { @@ -48,10 +49,6 @@ const MessageContainer: React.FC = ({ }; }, []); - const messageListClass = classNames(styles.messageList, { - [styles.miniProgramLoading]: miniProgramLoading, - }); - const getFollowQuestions = (index: number) => { const followQuestions: string[] = []; const currentMsg = messageList[index]; @@ -82,8 +79,7 @@ const MessageContainer: React.FC = ({ return (
- {miniProgramLoading && } -
+
{messageList.map((msgItem: MessageItem, index: number) => { const { id: msgId, domainId, type, msg, msgValue, identityMsg, msgData } = msgItem; @@ -91,6 +87,9 @@ const MessageContainer: React.FC = ({ return (
+ {type === MessageTypeEnum.RECOMMEND_QUESTIONS && ( + + )} {type === MessageTypeEnum.TEXT && } {type === MessageTypeEnum.QUESTION && ( <> @@ -108,8 +107,6 @@ const MessageContainer: React.FC = ({ onMsgDataLoaded={(data: MsgDataType) => { onMsgDataLoaded(data, msgId); }} - onSelectSuggestion={onSelectSuggestion} - onUpdateMessageScroll={onUpdateMessageScroll} /> )} @@ -125,7 +122,7 @@ function areEqual(prevProps: Props, nextProps: Props) { if ( prevProps.id === nextProps.id && isEqual(prevProps.messageList, nextProps.messageList) && - prevProps.miniProgramLoading === nextProps.miniProgramLoading + prevProps.conversationCollapsed === nextProps.conversationCollapsed ) { return true; } diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/index.tsx deleted file mode 100644 index fad3e774c..000000000 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { CloseOutlined } from '@ant-design/icons'; -import moment from 'moment'; -import type { ConversationDetailType } from '../../../type'; -import styles from './style.less'; - -type Props = { - conversations: ConversationDetailType[]; - onSelectConversation: (conversation: ConversationDetailType) => void; - onClose: () => void; -}; - -const ConversationHistory: React.FC = ({ conversations, onSelectConversation, onClose }) => { - return ( -
-
-
历史记录
- -
-
- {conversations.slice(0, 1000).map((conversation) => { - return ( -
{ - onSelectConversation(conversation); - }} - > -
- {conversation.chatName} -
-
- 更新时间:{moment(conversation.lastTime).format('YYYY-MM-DD')} -
-
- ); - })} -
-
- ); -}; - -export default ConversationHistory; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/style.less deleted file mode 100644 index 29876f5d9..000000000 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/ConversationHistory/style.less +++ /dev/null @@ -1,64 +0,0 @@ -.conversationHistory { - position: absolute; - top: 0; - left: 0; - z-index: 10; - display: flex; - flex-direction: column; - width: 100%; - height: calc(100vh - 78px); - overflow: hidden; - background: #f3f3f7; - border-right: 1px solid var(--border-color-base); - - .header { - display: flex; - align-items: center; - justify-content: space-between; - height: 50px; - padding: 0 16px; - border-bottom: 1px solid #e8e8e8; - .headerTitle { - color: var(--text-color); - font-weight: 500; - font-size: 16px; - } - .headerClose { - color: var(--text-color-third); - font-size: 16px; - cursor: pointer; - &:hover { - color: var(--chat-blue); - } - } - } - .conversationContent { - flex: 1; - overflow: auto; - .conversationItem { - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 8px 16px; - border-bottom: 1px solid var(--border-color-base-bg-5); - cursor: pointer; - row-gap: 2px; - - &:hover { - background: var(--light-blue-background); - } - .conversationName { - width: 170px; - overflow: hidden; - color: var(--text-color); - font-size: 14px; - white-space: nowrap; - text-overflow: ellipsis; - } - .conversationTime { - color: var(--text-color-third); - font-size: 13px; - } - } - } -} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/index.tsx deleted file mode 100644 index 15c91f4b7..000000000 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/index.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import IconFont from '@/components/IconFont'; -import { Dropdown, Menu } from 'antd'; -import classNames from 'classnames'; -import { - useEffect, - useState, - forwardRef, - ForwardRefRenderFunction, - useImperativeHandle, -} from 'react'; -import { useLocation } from 'umi'; -import ConversationHistory from './ConversationHistory'; -import ConversationModal from './ConversationModal'; -import { deleteConversation, getAllConversations, saveConversation } from '../../service'; -import styles from './style.less'; -import { ConversationDetailType } from '../../type'; -import { DEFAULT_CONVERSATION_NAME } from '../../constants'; - -type Props = { - currentConversation?: ConversationDetailType; - onSelectConversation: (conversation: ConversationDetailType, name?: string) => void; -}; - -const Conversation: ForwardRefRenderFunction = ( - { currentConversation, onSelectConversation }, - ref, -) => { - const location = useLocation(); - const { q, cid } = (location as any).query; - const [originConversations, setOriginConversations] = useState([]); - const [conversations, setConversations] = useState([]); - const [editModalVisible, setEditModalVisible] = useState(false); - const [editConversation, setEditConversation] = useState(); - const [historyVisible, setHistoryVisible] = useState(false); - - useImperativeHandle(ref, () => ({ - updateData, - onAddConversation, - })); - - const updateData = async () => { - const { data } = await getAllConversations(); - const conversationList = (data || []).slice(0, 5); - setOriginConversations(data || []); - setConversations(conversationList); - return conversationList; - }; - - const initData = async () => { - const data = await updateData(); - if (data.length > 0) { - const chatId = localStorage.getItem('CONVERSATION_ID') || cid; - if (chatId) { - const conversation = data.find((item: any) => item.chatId === +chatId); - if (conversation) { - onSelectConversation(conversation); - } else { - onSelectConversation(data[0]); - } - } else { - onSelectConversation(data[0]); - } - } else { - onAddConversation(); - } - }; - - useEffect(() => { - if (q && cid === undefined && location.pathname === '/workbench/chat') { - onAddConversation(q); - } else { - initData(); - } - }, [q]); - - const addConversation = async (name?: string) => { - await saveConversation(name || DEFAULT_CONVERSATION_NAME); - return updateData(); - }; - - const onDeleteConversation = async (id: number) => { - await deleteConversation(id); - initData(); - }; - - const onAddConversation = async (name?: string) => { - const data = await addConversation(name); - onSelectConversation(data[0], name); - }; - - const onOperate = (key: string, conversation: ConversationDetailType) => { - if (key === 'editName') { - setEditConversation(conversation); - setEditModalVisible(true); - } else if (key === 'delete') { - onDeleteConversation(conversation.chatId); - } - }; - - const onShowHistory = () => { - setHistoryVisible(true); - }; - - return ( -
-
-
对话管理
-
- {conversations.map((item) => { - const conversationItemClass = classNames(styles.conversationItem, { - [styles.activeConversationItem]: currentConversation?.chatId === item.chatId, - }); - return ( - { - onOperate(key, item); - }} - /> - } - trigger={['contextMenu']} - > -
{ - onSelectConversation(item); - }} - > -
- -
- {item.chatName} -
-
-
-
- ); - })} -
-
- -
查看更多对话
-
-
-
-
- {historyVisible && ( - { - onSelectConversation(conversation); - setHistoryVisible(false); - }} - onClose={() => { - setHistoryVisible(false); - }} - /> - )} - { - setEditModalVisible(false); - }} - onFinish={() => { - setEditModalVisible(false); - updateData(); - }} - /> -
- ); -}; - -export default forwardRef(Conversation); diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/style.less deleted file mode 100644 index f8d493ccb..000000000 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/Conversation/style.less +++ /dev/null @@ -1,50 +0,0 @@ -.conversation { - position: relative; - margin-top: 30px; - padding: 0 10px; - - .conversationSection { - width: 100%; - height: 100%; - - .sectionTitle { - margin-bottom: 12px; - color: var(--text-color); - font-size: 16px; - line-height: 24px; - } - - .conversationList { - .conversationItem { - cursor: pointer; - .conversationItemContent { - display: flex; - align-items: center; - padding: 10px 0; - color: var(--text-color-third); - - .conversationIcon { - margin-right: 10px; - color: var(--text-color-fourth); - font-size: 20px; - } - - .conversationContent { - width: 160px; - overflow: hidden; - color: var(--text-color-third); - white-space: nowrap; - text-overflow: ellipsis; - } - } - - &.activeConversationItem, - &:hover { - .conversationContent { - color: var(--chat-blue); - } - } - } - } - } -} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/index.tsx index cea054fbe..bbb518123 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/RightSection/index.tsx @@ -6,7 +6,7 @@ import type { MsgDataType } from 'supersonic-chat-sdk'; import Domains from './Domains'; import { ConversationDetailType, DomainType } from '../type'; import DomainInfo from './Context/DomainInfo'; -import Conversation from './Conversation'; +import Conversation from '../Conversation'; type Props = { domains: DomainType[]; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/RecommendQuestions/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/components/RecommendQuestions/index.tsx new file mode 100644 index 000000000..19026a2bc --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/RecommendQuestions/index.tsx @@ -0,0 +1,64 @@ +import { useEffect, useState } from 'react'; +import LeftAvatar from '../LeftAvatar'; +import Message from '../Message'; +import styles from './style.less'; +import { queryRecommendQuestions } from '../../service'; +import Typing from '../Typing'; + +type Props = { + onSelectQuestion: (value: string) => void; +}; + +const RecommendQuestions: React.FC = ({ onSelectQuestion }) => { + const [questions, setQuestions] = useState([]); + const [loading, setLoading] = useState(false); + + const initData = async () => { + setLoading(true); + const res = await queryRecommendQuestions(); + setLoading(false); + setQuestions( + res.data?.reduce((result: any[], item: any) => { + result = [ + ...result, + ...item.recommendedQuestions.slice(0, 20).map((item: any) => item.question), + ]; + return result; + }, []) || [], + ); + }; + + useEffect(() => { + initData(); + }, []); + + return ( +
+ + {loading ? ( + + ) : questions.length > 0 ? ( + +
推荐问题:
+
+ {questions.map((question, index) => ( +
{ + onSelectQuestion(question); + }} + > + {question} +
+ ))} +
+
+ ) : ( + 您好,请问有什么我可以帮您吗? + )} +
+ ); +}; + +export default RecommendQuestions; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/RecommendQuestions/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/components/RecommendQuestions/style.less new file mode 100644 index 000000000..c7c4ba72a --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/RecommendQuestions/style.less @@ -0,0 +1,35 @@ +.recommendQuestions { + display: flex; + + .recommendQuestionsMsg { + padding: 12px 20px 20px !important; + + .title { + font-size: 14px; + font-weight: 500; + margin-bottom: 12px; + } + + .content { + display: flex; + align-items: center; + column-gap: 16px; + row-gap: 20px; + + .question { + padding: 0 6px; + height: 22px; + line-height: 22px; + font-size: 12px; + color: var(--text-color); + border-radius:11px; + background-color: #f4f4f4; + cursor: pointer; + + &:hover { + color: var(--chat-blue); + } + } + } + } +} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/Suggestion/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/components/Suggestion/index.tsx new file mode 100644 index 000000000..b57dbe873 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/Suggestion/index.tsx @@ -0,0 +1,58 @@ +import { updateMessageContainerScroll } from '@/utils/utils'; +import { useEffect, useState } from 'react'; +import { querySuggestion } from '../../service'; +import { SuggestionType } from '../../type'; +import Message from '../Message'; +import styles from './style.less'; + +type Props = { + domainId: number; + onSelectSuggestion: (value: string) => void; +}; + +const Suggestion: React.FC = ({ domainId, onSelectSuggestion }) => { + const [data, setData] = useState({ dimensions: [], metrics: [] }); + const { metrics } = data; + + const initData = async () => { + const res = await querySuggestion(domainId); + setData({ + dimensions: res.data.dimensions.slice(0, 5), + metrics: res.data.metrics.slice(0, 5), + }); + updateMessageContainerScroll(); + }; + + useEffect(() => { + initData(); + }, []); + + return ( +
+ +
+
您可能还想问以下指标:
+
+ {metrics.map((metric, index) => { + return ( + <> + { + onSelectSuggestion(metric.name); + }} + > + {metric.name} + + {index !== metrics.length - 1 && } + + ); + })} +
+
+
+
+ ); +}; + +export default Suggestion; diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/Suggestion/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/components/Suggestion/style.less new file mode 100644 index 000000000..ebcf8f209 --- /dev/null +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/Suggestion/style.less @@ -0,0 +1,37 @@ +.suggestion { + margin-left: 46px; + + .suggestionMsg { + display: flex; + flex-direction: column; + padding: 16px !important; + row-gap: 12px; + + .row { + display: flex; + flex-wrap: wrap; + align-items: center; + column-gap: 4px; + row-gap: 12px; + + .rowTitle { + color: var(--text-color-third); + } + + .rowContent { + display: flex; + flex-wrap: wrap; + align-items: center; + color: var(--text-color); + row-gap: 12px; + + .contentItemName { + color: var(--chat-blue); + font-weight: 500; + border-bottom: 1px solid var(--chat-blue); + cursor: pointer; + } + } + } + } +} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/components/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/components/style.less index 0fc5f2dac..60b0c52f3 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/components/style.less +++ b/webapp/packages/supersonic-fe/src/pages/Chat/components/style.less @@ -229,6 +229,7 @@ .typingBubble { width: fit-content; + padding: 16px !important; } .quote { diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/index.tsx b/webapp/packages/supersonic-fe/src/pages/Chat/index.tsx index f12c15f38..239000c8b 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/index.tsx +++ b/webapp/packages/supersonic-fe/src/pages/Chat/index.tsx @@ -15,6 +15,7 @@ import { HistoryMsgItemType, MsgDataType, getHistoryMsg } from 'supersonic-chat- import 'supersonic-chat-sdk/dist/index.css'; import { setToken as setChatSdkToken } from 'supersonic-chat-sdk'; import { TOKEN_KEY } from '@/services/request'; +import Conversation from './Conversation'; type Props = { isCopilotMode?: boolean; @@ -35,6 +36,7 @@ const Chat: React.FC = ({ isCopilotMode }) => { const [miniProgramLoading, setMiniProgramLoading] = useState(false); const [domains, setDomains] = useState([]); const [currentDomain, setCurrentDomain] = useState(); + const [conversationCollapsed, setConversationCollapsed] = useState(false); const conversationRef = useRef(); const chatFooterRef = useRef(); @@ -42,14 +44,14 @@ const Chat: React.FC = ({ isCopilotMode }) => { setMessageList([ { id: uuid(), - type: MessageTypeEnum.TEXT, - msg: '您好,请问有什么我能帮您吗?', + type: MessageTypeEnum.RECOMMEND_QUESTIONS, + // msg: '您好,请问有什么我能帮您吗?', }, ]); }; const existInstuctionMsg = (list: HistoryMsgItemType[]) => { - return list.some((msg) => msg.queryResponse.queryMode === MessageTypeEnum.INSTRUCTION); + return list.some((msg) => msg.queryResult?.queryMode === MessageTypeEnum.INSTRUCTION); }; const updateScroll = (list: HistoryMsgItemType[]) => { @@ -71,11 +73,11 @@ const Chat: React.FC = ({ isCopilotMode }) => { ...list.map((item: HistoryMsgItemType) => ({ id: item.questionId, type: - item.queryResponse?.queryMode === MessageTypeEnum.INSTRUCTION + item.queryResult?.queryMode === MessageTypeEnum.INSTRUCTION ? MessageTypeEnum.INSTRUCTION : MessageTypeEnum.QUESTION, msg: item.queryText, - msgData: item.queryResponse, + msgData: item.queryResult, isHistory: true, })), ...(page === 1 ? [] : messageList), @@ -85,7 +87,7 @@ const Chat: React.FC = ({ isCopilotMode }) => { if (list.length === 0) { sendHelloRsp(); } else { - setCurrentEntity(list[list.length - 1].queryResponse); + setCurrentEntity(list[list.length - 1].queryResult); } updateScroll(list); setHistoryInited(true); @@ -203,6 +205,10 @@ const Chat: React.FC = ({ isCopilotMode }) => { modifyConversationName(currentMsg); }; + const onToggleCollapseBtn = () => { + setConversationCollapsed(!conversationCollapsed); + }; + const onInputMsgChange = (value: string) => { const inputMsgValue = value || ''; setInputMsg(inputMsgValue); @@ -295,6 +301,7 @@ const Chat: React.FC = ({ isCopilotMode }) => { const chatClass = classNames(styles.chat, { [styles.mobile]: isMobileMode, [styles.copilot]: isCopilotMode, + [styles.conversationCollapsed]: conversationCollapsed, }); return ( @@ -302,6 +309,12 @@ const Chat: React.FC = ({ isCopilotMode }) => { {!isMobileMode && }
+
{currentConversation && (
@@ -310,22 +323,23 @@ const Chat: React.FC = ({ isCopilotMode }) => { id="messageContainer" messageList={messageList} chatId={currentConversation?.chatId} - miniProgramLoading={miniProgramLoading} isMobileMode={isMobileMode} + conversationCollapsed={conversationCollapsed} onClickMessageContainer={() => { inputFocus(); }} onMsgDataLoaded={onMsgDataLoaded} onSelectSuggestion={onSendMsg} onCheckMore={onCheckMore} - onUpdateMessageScroll={updateMessageContainerScroll} /> { onSendMsg(msg, messageList, domainId); @@ -343,7 +357,7 @@ const Chat: React.FC = ({ isCopilotMode }) => {
)}
- {!isMobileMode && ( + {/* {!isMobileMode && ( = ({ isCopilotMode }) => { onSelectConversation={onSelectConversation} conversationRef={conversationRef} /> - )} + )} */}
); diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/service.ts b/webapp/packages/supersonic-fe/src/pages/Chat/service.ts index d76d861c7..ef8c651d5 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/service.ts +++ b/webapp/packages/supersonic-fe/src/pages/Chat/service.ts @@ -30,3 +30,24 @@ export function getDomainList() { skipErrorHandler: true, }); } + +export function updateQAFeedback(questionId: number, score: number) { + return request>( + `${prefix}/chat/manage/updateQAFeedback?id=${questionId}&score=${score}&feedback=`, + { + method: 'POST', + }, + ); +} + +export function querySuggestion(domainId: number) { + return request>(`${prefix}/chat/recommend/${domainId}`, { + method: 'GET', + }); +} + +export function queryRecommendQuestions() { + return request>(`${prefix}/chat/recommend/question`, { + method: 'GET', + }); +} diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/style.less b/webapp/packages/supersonic-fe/src/pages/Chat/style.less index b972e9357..12c059bcc 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/style.less +++ b/webapp/packages/supersonic-fe/src/pages/Chat/style.less @@ -16,9 +16,9 @@ .chatApp { display: flex; flex-direction: column; - width: calc(100vw - 225px); + width: calc(100vw - 260px); height: calc(100vh - 48px); - padding-left: 20px; + padding-left: 10px; color: rgba(0, 0, 0, 0.87); .emptyHolder { @@ -230,6 +230,12 @@ } } + &.conversationCollapsed { + .chatApp { + width: 100% !important; + } + } + &.mobile { height: 100% !important; @@ -243,7 +249,7 @@ } .chatApp { - width: calc(100% - 225px) !important; + width: 100% !important; height: 100% !important; margin-top: 0 !important; } diff --git a/webapp/packages/supersonic-fe/src/pages/Chat/type.ts b/webapp/packages/supersonic-fe/src/pages/Chat/type.ts index 975098d17..da43e83c6 100644 --- a/webapp/packages/supersonic-fe/src/pages/Chat/type.ts +++ b/webapp/packages/supersonic-fe/src/pages/Chat/type.ts @@ -6,6 +6,8 @@ export enum MessageTypeEnum { NO_PERMISSION = 'no_permission', // 无权限 SEMANTIC_DETAIL = 'semantic_detail', // 语义指标/维度等信息详情 INSTRUCTION = 'INSTRUCTION', // 插件 + SUGGESTION = 'SUGGESTION', + RECOMMEND_QUESTIONS = 'RECOMMEND_QUESTIONS' // 推荐问题 } export type MessageItem = { @@ -41,3 +43,15 @@ export type DomainType = { name: string; bizName: string; }; + +export type SuggestionItemType = { + id: number; + domain: number; + name: string; + bizName: string; +}; + +export type SuggestionType = { + dimensions: SuggestionItemType[]; + metrics: SuggestionItemType[]; +}; diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/CNAME b/webapp/packages/supersonic-fe/supersonic-webapp/CNAME deleted file mode 100644 index 30c2d4d36..000000000 --- a/webapp/packages/supersonic-fe/supersonic-webapp/CNAME +++ /dev/null @@ -1 +0,0 @@ -preview.pro.ant.design \ No newline at end of file diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/asset-manifest.json b/webapp/packages/supersonic-fe/supersonic-webapp/asset-manifest.json deleted file mode 100644 index f10a93c1d..000000000 --- a/webapp/packages/supersonic-fe/supersonic-webapp/asset-manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "/umi.css": "/webapp/umi.51119182.css", - "/umi.js": "/webapp/umi.5428eb1b.js", - "/public/CNAME": "/webapp/CNAME", - "/public/favicon.ico": "/webapp/favicon.ico", - "/public/home_bg.png": "/webapp/home_bg.png", - "/public/icons/icon-128x128.png": "/webapp/icons/icon-128x128.png", - "/public/icons/icon-192x192.png": "/webapp/icons/icon-192x192.png", - "/public/icons/icon-512x512.png": "/webapp/icons/icon-512x512.png", - "/index.html": "/webapp/index.html", - "/public/logo.svg": "/webapp/logo.svg", - "/public/pro_icon.svg": "/webapp/pro_icon.svg", - "/static/cloudEditor.svg": "/webapp/static/cloudEditor.1a9aa2c1.svg", - "/static/iconfont.woff2?t=1659425018463": "/webapp/static/iconfont.0ac2d58a.woff2", - "/static/iconfont.woff?t=1659425018463": "/webapp/static/iconfont.0de60a33.woff", - "/static/iconfont.ttf?t=1659425018463": "/webapp/static/iconfont.7ae6e4e0.ttf", - "/static/iconfont.svg?t=1659425018463": "/webapp/static/iconfont.92a3f736.svg", - "/public/supersonic.config.json": "/webapp/supersonic.config.json", - "/public/version.js": "/webapp/version.js" -} \ No newline at end of file diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/favicon.ico b/webapp/packages/supersonic-fe/supersonic-webapp/favicon.ico deleted file mode 100644 index 003e7a69b..000000000 Binary files a/webapp/packages/supersonic-fe/supersonic-webapp/favicon.ico and /dev/null differ diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/home_bg.png b/webapp/packages/supersonic-fe/supersonic-webapp/home_bg.png deleted file mode 100644 index 7c92a4bef..000000000 Binary files a/webapp/packages/supersonic-fe/supersonic-webapp/home_bg.png and /dev/null differ diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/icons/icon-128x128.png b/webapp/packages/supersonic-fe/supersonic-webapp/icons/icon-128x128.png deleted file mode 100644 index 48d0e2339..000000000 Binary files a/webapp/packages/supersonic-fe/supersonic-webapp/icons/icon-128x128.png and /dev/null differ diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/icons/icon-192x192.png b/webapp/packages/supersonic-fe/supersonic-webapp/icons/icon-192x192.png deleted file mode 100644 index 938e9b53f..000000000 Binary files a/webapp/packages/supersonic-fe/supersonic-webapp/icons/icon-192x192.png and /dev/null differ diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/icons/icon-512x512.png b/webapp/packages/supersonic-fe/supersonic-webapp/icons/icon-512x512.png deleted file mode 100644 index 21fc108f0..000000000 Binary files a/webapp/packages/supersonic-fe/supersonic-webapp/icons/icon-512x512.png and /dev/null differ diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/index.html b/webapp/packages/supersonic-fe/supersonic-webapp/index.html deleted file mode 100644 index 3bf96dc72..000000000 --- a/webapp/packages/supersonic-fe/supersonic-webapp/index.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - 超音数(SuperSonic) - - - - - - - - - - -
- - - diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/logo.svg b/webapp/packages/supersonic-fe/supersonic-webapp/logo.svg deleted file mode 100644 index 3afa34825..000000000 --- a/webapp/packages/supersonic-fe/supersonic-webapp/logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/pro_icon.svg b/webapp/packages/supersonic-fe/supersonic-webapp/pro_icon.svg deleted file mode 100644 index e075b78d7..000000000 --- a/webapp/packages/supersonic-fe/supersonic-webapp/pro_icon.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/static/cloudEditor.1a9aa2c1.svg b/webapp/packages/supersonic-fe/supersonic-webapp/static/cloudEditor.1a9aa2c1.svg deleted file mode 100644 index dad901278..000000000 --- a/webapp/packages/supersonic-fe/supersonic-webapp/static/cloudEditor.1a9aa2c1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.0ac2d58a.woff2 b/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.0ac2d58a.woff2 deleted file mode 100644 index a96e304bb..000000000 Binary files a/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.0ac2d58a.woff2 and /dev/null differ diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.0de60a33.woff b/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.0de60a33.woff deleted file mode 100644 index 0697b71d7..000000000 Binary files a/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.0de60a33.woff and /dev/null differ diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.7ae6e4e0.ttf b/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.7ae6e4e0.ttf deleted file mode 100644 index 4213979d5..000000000 Binary files a/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.7ae6e4e0.ttf and /dev/null differ diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.92a3f736.svg b/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.92a3f736.svg deleted file mode 100644 index 541ad45b4..000000000 --- a/webapp/packages/supersonic-fe/supersonic-webapp/static/iconfont.92a3f736.svg +++ /dev/null @@ -1,181 +0,0 @@ - - - - Created by iconfont - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/supersonic.config.json b/webapp/packages/supersonic-fe/supersonic-webapp/supersonic.config.json deleted file mode 100644 index 9195bb747..000000000 --- a/webapp/packages/supersonic-fe/supersonic-webapp/supersonic.config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "env": "" -} diff --git a/webapp/packages/supersonic-fe/supersonic-webapp/version.js b/webapp/packages/supersonic-fe/supersonic-webapp/version.js deleted file mode 100644 index 1fec802ad..000000000 --- a/webapp/packages/supersonic-fe/supersonic-webapp/version.js +++ /dev/null @@ -1,4 +0,0 @@ -feVersion={ - "commitId": "078a81038f60d1a220e26b78b67156ff406a484d", - "updateTime": "Sun Jul 30 2023 23:33:24 GMT+0800 (China Standard Time)" -} \ No newline at end of file diff --git a/webapp/start-fe-dev.sh b/webapp/start-fe-dev.sh index 165cc4bf5..d2b6f8f82 100755 --- a/webapp/start-fe-dev.sh +++ b/webapp/start-fe-dev.sh @@ -1,3 +1,5 @@ +rm -rf ./packages/supersonic-fe/src/.umi ./packages/supersonic-fe/src/.umi-production + npm i npx lerna add supersonic-chat-sdk --scope supersonic-fe diff --git a/webapp/start-fe-prod.sh b/webapp/start-fe-prod.sh index a36b55394..9758c1ae3 100755 --- a/webapp/start-fe-prod.sh +++ b/webapp/start-fe-prod.sh @@ -1,5 +1,7 @@ rm -rf supersonic-webapp.tar.gz +rm -rf ./packages/supersonic-fe/src/.umi ./packages/supersonic-fe/src/.umi-production + npm i npx lerna add supersonic-chat-sdk --scope supersonic-fe