mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 19:51:00 +00:00
feat(chat-sdk/chatitem): 消息支持导出图表图片 (#1937)
This commit is contained in:
@@ -9,10 +9,10 @@ import {
|
|||||||
RangeValue,
|
RangeValue,
|
||||||
SimilarQuestionType,
|
SimilarQuestionType,
|
||||||
} from '../../common/type';
|
} from '../../common/type';
|
||||||
import { useEffect, useState } from 'react';
|
import { createContext, useEffect, useRef, useState } from 'react';
|
||||||
import { chatExecute, chatParse, queryData, deleteQuery, switchEntity } from '../../service';
|
import { chatExecute, chatParse, queryData, deleteQuery, switchEntity } from '../../service';
|
||||||
import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/constants';
|
import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/constants';
|
||||||
import { Spin } from 'antd';
|
import { message, Spin } from 'antd';
|
||||||
import IconFont from '../IconFont';
|
import IconFont from '../IconFont';
|
||||||
import ExpandParseTip from './ExpandParseTip';
|
import ExpandParseTip from './ExpandParseTip';
|
||||||
import ParseTip from './ParseTip';
|
import ParseTip from './ParseTip';
|
||||||
@@ -25,6 +25,7 @@ import SimilarQuestionItem from './SimilarQuestionItem';
|
|||||||
import { AgentType } from '../../Chat/type';
|
import { AgentType } from '../../Chat/type';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { exportCsvFile } from '../../utils/utils';
|
import { exportCsvFile } from '../../utils/utils';
|
||||||
|
import { useMethodRegister } from '../../hooks';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
msg: string;
|
msg: string;
|
||||||
@@ -51,6 +52,11 @@ type Props = {
|
|||||||
onSendMsg?: (msg: string) => void;
|
onSendMsg?: (msg: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ChartItemContext = createContext({
|
||||||
|
register: (...args: any[]) => {},
|
||||||
|
call: (...args: any[]) => {},
|
||||||
|
});
|
||||||
|
|
||||||
const ChatItem: React.FC<Props> = ({
|
const ChatItem: React.FC<Props> = ({
|
||||||
msg,
|
msg,
|
||||||
conversationId,
|
conversationId,
|
||||||
@@ -433,126 +439,135 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { llmReq, llmResp } = parseInfo?.properties?.CONTEXT || {};
|
const { llmReq, llmResp } = parseInfo?.properties?.CONTEXT || {};
|
||||||
|
|
||||||
|
const { register, call } = useMethodRegister(() => message.error('该条消息暂不支持该操作'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={prefixCls}>
|
<ChartItemContext.Provider value={{ register, call }}>
|
||||||
{!isMobile && <IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />}
|
<div className={prefixCls}>
|
||||||
<div className={isMobile ? `${prefixCls}-mobile-msg-card` : ''}>
|
{!isMobile && <IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />}
|
||||||
<div className={`${prefixCls}-time`}>
|
<div className={isMobile ? `${prefixCls}-mobile-msg-card` : ''}>
|
||||||
{parseTimeCost?.parseStartTime
|
<div className={`${prefixCls}-time`}>
|
||||||
? dayjs(parseTimeCost.parseStartTime).format('M月D日 HH:mm')
|
{parseTimeCost?.parseStartTime
|
||||||
: ''}
|
? dayjs(parseTimeCost.parseStartTime).format('M月D日 HH:mm')
|
||||||
</div>
|
: ''}
|
||||||
<div className={contentClass}>
|
</div>
|
||||||
<>
|
<div className={contentClass}>
|
||||||
{currentAgent?.enableFeedback === 1 && !questionId && showExpandParseTip && (
|
<>
|
||||||
<div style={{ marginBottom: 10 }}>
|
{currentAgent?.enableFeedback === 1 && !questionId && showExpandParseTip && (
|
||||||
<ExpandParseTip
|
<div style={{ marginBottom: 10 }}>
|
||||||
|
<ExpandParseTip
|
||||||
|
isSimpleMode={isSimpleMode}
|
||||||
|
parseInfoOptions={preParseInfoOptions}
|
||||||
|
agentId={agentId}
|
||||||
|
integrateSystem={integrateSystem}
|
||||||
|
parseTimeCost={parseTimeCost?.parseTime}
|
||||||
|
isDeveloper={isDeveloper}
|
||||||
|
onSelectParseInfo={onExpandSelectParseInfo}
|
||||||
|
onSwitchEntity={onSwitchEntity}
|
||||||
|
onFiltersChange={onFiltersChange}
|
||||||
|
onDateInfoChange={onDateInfoChange}
|
||||||
|
onRefresh={onRefresh}
|
||||||
|
handlePresetClick={handlePresetClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!preParseMode && (
|
||||||
|
<ParseTip
|
||||||
isSimpleMode={isSimpleMode}
|
isSimpleMode={isSimpleMode}
|
||||||
parseInfoOptions={preParseInfoOptions}
|
parseLoading={parseLoading}
|
||||||
|
parseInfoOptions={parseInfoOptions}
|
||||||
|
parseTip={parseTip}
|
||||||
|
currentParseInfo={parseInfo}
|
||||||
agentId={agentId}
|
agentId={agentId}
|
||||||
|
dimensionFilters={dimensionFilters}
|
||||||
|
dateInfo={dateInfo}
|
||||||
|
entityInfo={entityInfo}
|
||||||
integrateSystem={integrateSystem}
|
integrateSystem={integrateSystem}
|
||||||
parseTimeCost={parseTimeCost?.parseTime}
|
parseTimeCost={parseTimeCost?.parseTime}
|
||||||
isDeveloper={isDeveloper}
|
isDeveloper={isDeveloper}
|
||||||
onSelectParseInfo={onExpandSelectParseInfo}
|
onSelectParseInfo={onSelectParseInfo}
|
||||||
onSwitchEntity={onSwitchEntity}
|
onSwitchEntity={onSwitchEntity}
|
||||||
onFiltersChange={onFiltersChange}
|
onFiltersChange={onFiltersChange}
|
||||||
onDateInfoChange={onDateInfoChange}
|
onDateInfoChange={onDateInfoChange}
|
||||||
onRefresh={onRefresh}
|
onRefresh={() => {
|
||||||
|
onRefresh();
|
||||||
|
}}
|
||||||
handlePresetClick={handlePresetClick}
|
handlePresetClick={handlePresetClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
)}
|
</>
|
||||||
|
|
||||||
{!preParseMode && (
|
{executeMode && (
|
||||||
<ParseTip
|
<Spin spinning={entitySwitchLoading}>
|
||||||
isSimpleMode={isSimpleMode}
|
<div style={{ minHeight: 50 }}>
|
||||||
parseLoading={parseLoading}
|
{!isMobile &&
|
||||||
parseInfoOptions={parseInfoOptions}
|
parseInfo?.sqlInfo &&
|
||||||
parseTip={parseTip}
|
isDeveloper &&
|
||||||
currentParseInfo={parseInfo}
|
isDebugMode &&
|
||||||
agentId={agentId}
|
!isSimpleMode && (
|
||||||
dimensionFilters={dimensionFilters}
|
<SqlItem
|
||||||
dateInfo={dateInfo}
|
agentId={agentId}
|
||||||
entityInfo={entityInfo}
|
queryId={parseInfo.queryId}
|
||||||
integrateSystem={integrateSystem}
|
question={msg}
|
||||||
parseTimeCost={parseTimeCost?.parseTime}
|
llmReq={llmReq}
|
||||||
isDeveloper={isDeveloper}
|
llmResp={llmResp}
|
||||||
onSelectParseInfo={onSelectParseInfo}
|
integrateSystem={integrateSystem}
|
||||||
onSwitchEntity={onSwitchEntity}
|
queryMode={parseInfo.queryMode}
|
||||||
onFiltersChange={onFiltersChange}
|
sqlInfo={parseInfo.sqlInfo}
|
||||||
onDateInfoChange={onDateInfoChange}
|
sqlTimeCost={parseTimeCost?.sqlTime}
|
||||||
onRefresh={() => {
|
executeErrorMsg={executeErrorMsg}
|
||||||
onRefresh();
|
/>
|
||||||
}}
|
)}
|
||||||
handlePresetClick={handlePresetClick}
|
<ExecuteItem
|
||||||
/>
|
isSimpleMode={isSimpleMode}
|
||||||
)}
|
queryId={parseInfo?.queryId}
|
||||||
</>
|
|
||||||
|
|
||||||
{executeMode && (
|
|
||||||
<Spin spinning={entitySwitchLoading}>
|
|
||||||
<div style={{ minHeight: 50 }}>
|
|
||||||
{!isMobile && parseInfo?.sqlInfo && isDeveloper && isDebugMode && !isSimpleMode && (
|
|
||||||
<SqlItem
|
|
||||||
agentId={agentId}
|
|
||||||
queryId={parseInfo.queryId}
|
|
||||||
question={msg}
|
question={msg}
|
||||||
llmReq={llmReq}
|
queryMode={parseInfo?.queryMode}
|
||||||
llmResp={llmResp}
|
executeLoading={executeLoading}
|
||||||
integrateSystem={integrateSystem}
|
executeTip={executeTip}
|
||||||
queryMode={parseInfo.queryMode}
|
|
||||||
sqlInfo={parseInfo.sqlInfo}
|
|
||||||
sqlTimeCost={parseTimeCost?.sqlTime}
|
|
||||||
executeErrorMsg={executeErrorMsg}
|
executeErrorMsg={executeErrorMsg}
|
||||||
|
chartIndex={0}
|
||||||
|
data={data}
|
||||||
|
triggerResize={triggerResize}
|
||||||
|
executeItemNode={executeItemNode}
|
||||||
|
isDeveloper={isDeveloper}
|
||||||
|
renderCustomExecuteNode={renderCustomExecuteNode}
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
<ExecuteItem
|
</Spin>
|
||||||
isSimpleMode={isSimpleMode}
|
)}
|
||||||
|
{executeMode &&
|
||||||
|
!executeLoading &&
|
||||||
|
!isSimpleMode &&
|
||||||
|
parseInfo?.queryMode !== 'PLAIN_TEXT' && (
|
||||||
|
<SimilarQuestionItem
|
||||||
queryId={parseInfo?.queryId}
|
queryId={parseInfo?.queryId}
|
||||||
question={msg}
|
defaultExpanded={parseTip !== '' || executeTip !== ''}
|
||||||
queryMode={parseInfo?.queryMode}
|
similarQueries={data?.similarQueries}
|
||||||
executeLoading={executeLoading}
|
onSelectQuestion={onSelectQuestion}
|
||||||
executeTip={executeTip}
|
|
||||||
executeErrorMsg={executeErrorMsg}
|
|
||||||
chartIndex={0}
|
|
||||||
data={data}
|
|
||||||
triggerResize={triggerResize}
|
|
||||||
executeItemNode={executeItemNode}
|
|
||||||
isDeveloper={isDeveloper}
|
|
||||||
renderCustomExecuteNode={renderCustomExecuteNode}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
</Spin>
|
</div>
|
||||||
)}
|
{(parseTip !== '' || (executeMode && !executeLoading)) &&
|
||||||
{executeMode &&
|
|
||||||
!executeLoading &&
|
|
||||||
!isSimpleMode &&
|
|
||||||
parseInfo?.queryMode !== 'PLAIN_TEXT' && (
|
parseInfo?.queryMode !== 'PLAIN_TEXT' && (
|
||||||
<SimilarQuestionItem
|
<Tools
|
||||||
queryId={parseInfo?.queryId}
|
isLastMessage={isLastMessage}
|
||||||
defaultExpanded={parseTip !== '' || executeTip !== ''}
|
queryId={parseInfo?.queryId || 0}
|
||||||
similarQueries={data?.similarQueries}
|
scoreValue={score}
|
||||||
onSelectQuestion={onSelectQuestion}
|
isParserError={isParserError}
|
||||||
|
onExportData={() => {
|
||||||
|
onExportData();
|
||||||
|
}}
|
||||||
|
isSimpleMode={isSimpleMode}
|
||||||
|
onReExecute={queryId => {
|
||||||
|
deleteQueryInfo(queryId);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{(parseTip !== '' || (executeMode && !executeLoading)) &&
|
|
||||||
parseInfo?.queryMode !== 'PLAIN_TEXT' && (
|
|
||||||
<Tools
|
|
||||||
isLastMessage={isLastMessage}
|
|
||||||
queryId={parseInfo?.queryId || 0}
|
|
||||||
scoreValue={score}
|
|
||||||
isParserError={isParserError}
|
|
||||||
onExportData={() => {
|
|
||||||
onExportData();
|
|
||||||
}}
|
|
||||||
onReExecute={queryId => {
|
|
||||||
deleteQueryInfo(queryId);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ChartItemContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,19 @@ import {
|
|||||||
} from '../../../utils/utils';
|
} from '../../../utils/utils';
|
||||||
import type { ECharts } from 'echarts';
|
import type { ECharts } from 'echarts';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import {
|
||||||
|
forwardRef,
|
||||||
|
ForwardRefRenderFunction,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
import NoPermissionChart from '../NoPermissionChart';
|
import NoPermissionChart from '../NoPermissionChart';
|
||||||
import { ColumnType } from '../../../common/type';
|
import { ColumnType } from '../../../common/type';
|
||||||
import { Spin } from 'antd';
|
import { Spin } from 'antd';
|
||||||
|
import { ChartItemContext } from '../../ChatItem';
|
||||||
|
import { useExportByEcharts } from '../../../hooks';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
data: MsgDataType;
|
data: MsgDataType;
|
||||||
@@ -30,7 +39,7 @@ const BarChart: React.FC<Props> = ({
|
|||||||
onApplyAuth,
|
onApplyAuth,
|
||||||
}) => {
|
}) => {
|
||||||
const chartRef = useRef<any>();
|
const chartRef = useRef<any>();
|
||||||
const [instance, setInstance] = useState<ECharts>();
|
const instanceRef = useRef<ECharts>();
|
||||||
|
|
||||||
const { queryColumns, queryResults, entityInfo } = data;
|
const { queryColumns, queryResults, entityInfo } = data;
|
||||||
|
|
||||||
@@ -41,11 +50,11 @@ const BarChart: React.FC<Props> = ({
|
|||||||
|
|
||||||
const renderChart = () => {
|
const renderChart = () => {
|
||||||
let instanceObj: any;
|
let instanceObj: any;
|
||||||
if (!instance) {
|
if (!instanceRef.current) {
|
||||||
instanceObj = echarts.init(chartRef.current);
|
instanceObj = echarts.init(chartRef.current);
|
||||||
setInstance(instanceObj);
|
instanceRef.current = instanceObj;
|
||||||
} else {
|
} else {
|
||||||
instanceObj = instance;
|
instanceObj = instanceRef.current;
|
||||||
}
|
}
|
||||||
const data = (queryResults || []).sort(
|
const data = (queryResults || []).sort(
|
||||||
(a: any, b: any) => b[metricColumnName] - a[metricColumnName]
|
(a: any, b: any) => b[metricColumnName] - a[metricColumnName]
|
||||||
@@ -163,8 +172,8 @@ const BarChart: React.FC<Props> = ({
|
|||||||
}, [queryResults]);
|
}, [queryResults]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (triggerResize && instance) {
|
if (triggerResize && instanceRef.current) {
|
||||||
instance.resize();
|
instanceRef.current.resize();
|
||||||
}
|
}
|
||||||
}, [triggerResize]);
|
}, [triggerResize]);
|
||||||
|
|
||||||
@@ -180,6 +189,15 @@ const BarChart: React.FC<Props> = ({
|
|||||||
|
|
||||||
const prefixCls = `${PREFIX_CLS}-bar`;
|
const prefixCls = `${PREFIX_CLS}-bar`;
|
||||||
|
|
||||||
|
const { downloadChartAsImage } = useExportByEcharts({
|
||||||
|
instanceRef,
|
||||||
|
question,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { register } = useContext(ChartItemContext);
|
||||||
|
|
||||||
|
register('downloadChartAsImage', downloadChartAsImage);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={`${prefixCls}-top-bar`}>
|
<div className={`${prefixCls}-top-bar`}>
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import {
|
|||||||
} from '../../../utils/utils';
|
} from '../../../utils/utils';
|
||||||
import type { ECharts } from 'echarts';
|
import type { ECharts } from 'echarts';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ColumnType } from '../../../common/type';
|
import { ColumnType } from '../../../common/type';
|
||||||
import NoPermissionChart from '../NoPermissionChart';
|
import NoPermissionChart from '../NoPermissionChart';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isArray } from 'lodash';
|
import { isArray } from 'lodash';
|
||||||
|
import { useExportByEcharts } from '../../../hooks';
|
||||||
|
import { ChartItemContext } from '../../ChatItem';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model?: string;
|
model?: string;
|
||||||
@@ -37,15 +39,15 @@ const MetricTrendChart: React.FC<Props> = ({
|
|||||||
chartType,
|
chartType,
|
||||||
}) => {
|
}) => {
|
||||||
const chartRef = useRef<any>();
|
const chartRef = useRef<any>();
|
||||||
const [instance, setInstance] = useState<ECharts>();
|
const instanceRef = useRef<ECharts>();
|
||||||
|
|
||||||
const renderChart = () => {
|
const renderChart = () => {
|
||||||
let instanceObj: any;
|
let instanceObj: any;
|
||||||
if (!instance) {
|
if (!instanceRef.current) {
|
||||||
instanceObj = echarts.init(chartRef.current);
|
instanceObj = echarts.init(chartRef.current);
|
||||||
setInstance(instanceObj);
|
instanceRef.current = instanceObj;
|
||||||
} else {
|
} else {
|
||||||
instanceObj = instance;
|
instanceObj = instanceRef.current;
|
||||||
instanceObj.clear();
|
instanceObj.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +197,15 @@ const MetricTrendChart: React.FC<Props> = ({
|
|||||||
instanceObj.resize();
|
instanceObj.resize();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { downloadChartAsImage } = useExportByEcharts({
|
||||||
|
instanceRef,
|
||||||
|
question: metricField.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { register } = useContext(ChartItemContext);
|
||||||
|
|
||||||
|
register('downloadChartAsImage', downloadChartAsImage);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (metricField.authorized) {
|
if (metricField.authorized) {
|
||||||
renderChart();
|
renderChart();
|
||||||
@@ -202,8 +213,8 @@ const MetricTrendChart: React.FC<Props> = ({
|
|||||||
}, [resultList, metricField, chartType]);
|
}, [resultList, metricField, chartType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (triggerResize && instance) {
|
if (triggerResize && instanceRef.current) {
|
||||||
instance.resize();
|
instanceRef.current.resize();
|
||||||
}
|
}
|
||||||
}, [triggerResize]);
|
}, [triggerResize]);
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import { CHART_SECONDARY_COLOR, CLS_PREFIX, THEME_COLOR_LIST } from '../../../co
|
|||||||
import { getFormattedValue } from '../../../utils/utils';
|
import { getFormattedValue } from '../../../utils/utils';
|
||||||
import type { ECharts } from 'echarts';
|
import type { ECharts } from 'echarts';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { ColumnType } from '../../../common/type';
|
import { ColumnType } from '../../../common/type';
|
||||||
import { isArray } from 'lodash';
|
import { isArray } from 'lodash';
|
||||||
|
import { ChartItemContext } from '../../ChatItem';
|
||||||
|
import { useExportByEcharts } from '../../../hooks';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
dateColumnName: string;
|
dateColumnName: string;
|
||||||
@@ -13,6 +15,7 @@ type Props = {
|
|||||||
resultList: any[];
|
resultList: any[];
|
||||||
triggerResize?: boolean;
|
triggerResize?: boolean;
|
||||||
chartType?: string;
|
chartType?: string;
|
||||||
|
question: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MultiMetricsTrendChart: React.FC<Props> = ({
|
const MultiMetricsTrendChart: React.FC<Props> = ({
|
||||||
@@ -21,17 +24,17 @@ const MultiMetricsTrendChart: React.FC<Props> = ({
|
|||||||
resultList,
|
resultList,
|
||||||
triggerResize,
|
triggerResize,
|
||||||
chartType,
|
chartType,
|
||||||
|
question,
|
||||||
}) => {
|
}) => {
|
||||||
const chartRef = useRef<any>();
|
const chartRef = useRef<any>();
|
||||||
const [instance, setInstance] = useState<ECharts>();
|
const instanceRef = useRef<ECharts>();
|
||||||
|
|
||||||
const renderChart = () => {
|
const renderChart = () => {
|
||||||
let instanceObj: any;
|
let instanceObj: any;
|
||||||
if (!instance) {
|
if (!instanceRef.current) {
|
||||||
instanceObj = echarts.init(chartRef.current);
|
instanceObj = echarts.init(chartRef.current);
|
||||||
setInstance(instanceObj);
|
instanceRef.current = instanceObj;
|
||||||
} else {
|
} else {
|
||||||
instanceObj = instance;
|
instanceObj = instanceRef.current;
|
||||||
instanceObj.clear();
|
instanceObj.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,13 +135,22 @@ const MultiMetricsTrendChart: React.FC<Props> = ({
|
|||||||
instanceObj.resize();
|
instanceObj.resize();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { downloadChartAsImage } = useExportByEcharts({
|
||||||
|
instanceRef,
|
||||||
|
question,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { register } = useContext(ChartItemContext);
|
||||||
|
|
||||||
|
register('downloadChartAsImage', downloadChartAsImage);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderChart();
|
renderChart();
|
||||||
}, [resultList, chartType]);
|
}, [resultList, chartType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (triggerResize && instance) {
|
if (triggerResize && instanceRef.current) {
|
||||||
instance.resize();
|
instanceRef.current.resize();
|
||||||
}
|
}
|
||||||
}, [triggerResize]);
|
}, [triggerResize]);
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ const MetricTrend: React.FC<Props> = ({
|
|||||||
<MultiMetricsTrendChart
|
<MultiMetricsTrendChart
|
||||||
dateColumnName={dateColumnName}
|
dateColumnName={dateColumnName}
|
||||||
metricFields={metricFields}
|
metricFields={metricFields}
|
||||||
|
question={question}
|
||||||
resultList={queryResults}
|
resultList={queryResults}
|
||||||
triggerResize={triggerResize}
|
triggerResize={triggerResize}
|
||||||
chartType={chartType}
|
chartType={chartType}
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
import { isMobile } from '../../utils/utils';
|
import { isMobile } from '../../utils/utils';
|
||||||
import { DislikeOutlined, LikeOutlined, DownloadOutlined, RedoOutlined } from '@ant-design/icons';
|
import {
|
||||||
|
DislikeOutlined,
|
||||||
|
LikeOutlined,
|
||||||
|
DownloadOutlined,
|
||||||
|
RedoOutlined,
|
||||||
|
FileJpgOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { CLS_PREFIX } from '../../common/constants';
|
import { CLS_PREFIX } from '../../common/constants';
|
||||||
import { useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { updateQAFeedback } from '../../service';
|
import { updateQAFeedback } from '../../service';
|
||||||
|
import { useMethodRegister } from '../../hooks';
|
||||||
|
import { ChartItemContext } from '../ChatItem';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
queryId: number;
|
queryId: number;
|
||||||
scoreValue?: number;
|
scoreValue?: number;
|
||||||
isLastMessage?: boolean;
|
isLastMessage?: boolean;
|
||||||
isParserError?: boolean;
|
isParserError?: boolean;
|
||||||
|
isSimpleMode?: boolean;
|
||||||
onExportData?: () => void;
|
onExportData?: () => void;
|
||||||
onReExecute?: (queryId: number) => void;
|
onReExecute?: (queryId: number) => void;
|
||||||
};
|
};
|
||||||
@@ -20,6 +29,7 @@ const Tools: React.FC<Props> = ({
|
|||||||
scoreValue,
|
scoreValue,
|
||||||
isLastMessage,
|
isLastMessage,
|
||||||
isParserError = false,
|
isParserError = false,
|
||||||
|
isSimpleMode = false,
|
||||||
onExportData,
|
onExportData,
|
||||||
onReExecute,
|
onReExecute,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -44,6 +54,8 @@ const Tools: React.FC<Props> = ({
|
|||||||
[`${prefixCls}-feedback-active`]: score === 1,
|
[`${prefixCls}-feedback-active`]: score === 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { call } = useContext(ChartItemContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={prefixCls}>
|
<div className={prefixCls}>
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
@@ -68,6 +80,18 @@ const Tools: React.FC<Props> = ({
|
|||||||
<DownloadOutlined />
|
<DownloadOutlined />
|
||||||
<span className={`${prefixCls}-font-style`}>导出数据</span>
|
<span className={`${prefixCls}-font-style`}>导出数据</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
{!isSimpleMode && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
call('downloadChartAsImage');
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<FileJpgOutlined />
|
||||||
|
<span className={`${prefixCls}-font-style`}>导出图片</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{isLastMessage && (
|
{isLastMessage && (
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
3
webapp/packages/chat-sdk/src/hooks/index.ts
Normal file
3
webapp/packages/chat-sdk/src/hooks/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './useMethodRegister';
|
||||||
|
export * from './useComposing';
|
||||||
|
export * from './useExportByEcharts';
|
||||||
41
webapp/packages/chat-sdk/src/hooks/useExportByEcharts.ts
Normal file
41
webapp/packages/chat-sdk/src/hooks/useExportByEcharts.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { message } from 'antd';
|
||||||
|
import { ECharts } from 'echarts';
|
||||||
|
|
||||||
|
export interface ExportByEchartsProps {
|
||||||
|
instanceRef: React.MutableRefObject<ECharts | undefined>;
|
||||||
|
question: string;
|
||||||
|
options?: Parameters<ECharts['getConnectedDataURL']>[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useExportByEcharts = ({ instanceRef, question, options }: ExportByEchartsProps) => {
|
||||||
|
const handleSaveAsImage = () => {
|
||||||
|
if (instanceRef.current) {
|
||||||
|
return instanceRef.current.getConnectedDataURL({
|
||||||
|
type: 'png',
|
||||||
|
pixelRatio: 2,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
excludeComponents: ['toolbox'],
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadImage = (url: string) => {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${question}.png`;
|
||||||
|
a.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadChartAsImage = () => {
|
||||||
|
const url = handleSaveAsImage();
|
||||||
|
if (url) {
|
||||||
|
downloadImage(url);
|
||||||
|
message.success('导出图片成功');
|
||||||
|
} else {
|
||||||
|
message.error('该条消息暂不支持导出图片');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { downloadChartAsImage };
|
||||||
|
};
|
||||||
25
webapp/packages/chat-sdk/src/hooks/useMethodRegister.ts
Normal file
25
webapp/packages/chat-sdk/src/hooks/useMethodRegister.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
export const useMethodRegister = (fallback?: (...args: any[]) => any) => {
|
||||||
|
const methodStore = useRef<Map<string, (...args: any[]) => any>>(new Map());
|
||||||
|
|
||||||
|
const register = useCallback<(key: string, method: (...args: any[]) => any) => any>(
|
||||||
|
(key, method) => {
|
||||||
|
methodStore.current.set(key, method);
|
||||||
|
},
|
||||||
|
[methodStore]
|
||||||
|
);
|
||||||
|
|
||||||
|
const call = useCallback<(key: string, ...args: any[]) => any>(
|
||||||
|
(key, ...args) => {
|
||||||
|
const method = methodStore.current.get(key);
|
||||||
|
if (method) {
|
||||||
|
return method(...args);
|
||||||
|
}
|
||||||
|
return fallback?.(...args);
|
||||||
|
},
|
||||||
|
[methodStore]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { register, call };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user