mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 11:07:06 +00:00
(feature)(chat-sdk) add export error msg and export data (#1803)
This commit is contained in:
@@ -148,6 +148,7 @@ export type MsgDataType = {
|
||||
similarQueries: SimilarQuestionType[];
|
||||
recommendedDimensions: DrillDownDimensionType[];
|
||||
textResult: string;
|
||||
errorMsg: string;
|
||||
};
|
||||
|
||||
export enum ParseStateEnum {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Spin, Row, Col, Switch } from 'antd';
|
||||
import { CheckCircleFilled } from '@ant-design/icons';
|
||||
import { Spin, Switch, Button } from 'antd';
|
||||
import { CheckCircleFilled, DownloadOutlined } from '@ant-design/icons';
|
||||
import { PREFIX_CLS, MsgContentTypeEnum } from '../../common/constants';
|
||||
import { MsgDataType } from '../../common/type';
|
||||
import ChatMsg from '../ChatMsg';
|
||||
import WebPage from '../ChatMsg/WebPage';
|
||||
import Loading from './Loading';
|
||||
import React, { ReactNode, useState } from 'react';
|
||||
import { exportCsvFile } from '../../utils/utils';
|
||||
|
||||
type Props = {
|
||||
queryId?: number;
|
||||
@@ -59,6 +60,20 @@ const ExecuteItem: React.FC<Props> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const onExportData = () => {
|
||||
const { queryColumns, queryResults } = data || {};
|
||||
if (!!queryResults) {
|
||||
const exportData = queryResults.map(item => {
|
||||
return Object.keys(item).reduce((result, key) => {
|
||||
const columnName = queryColumns?.find(column => column.nameEn === key)?.name || key;
|
||||
result[columnName] = item[key];
|
||||
return result;
|
||||
}, {});
|
||||
});
|
||||
exportCsvFile(exportData);
|
||||
}
|
||||
};
|
||||
|
||||
if (executeLoading) {
|
||||
return getNodeTip(`${titlePrefix}查询中`);
|
||||
}
|
||||
@@ -67,7 +82,7 @@ const ExecuteItem: React.FC<Props> = ({
|
||||
return getNodeTip(
|
||||
<>
|
||||
{titlePrefix}查询失败
|
||||
{data?.queryTimeCost && isDeveloper && (
|
||||
{!!data?.queryTimeCost && isDeveloper && (
|
||||
<span className={`${prefixCls}-title-tip`}>(耗时: {data.queryTimeCost}ms)</span>
|
||||
)}
|
||||
</>,
|
||||
@@ -83,28 +98,35 @@ const ExecuteItem: React.FC<Props> = ({
|
||||
<>
|
||||
<div className={`${prefixCls}-title-bar`}>
|
||||
<CheckCircleFilled className={`${prefixCls}-step-icon`} />
|
||||
<div className={`${prefixCls}-step-title`} style={{ width: '100%' }}>
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Col flex="1 1 auto">
|
||||
{titlePrefix}查询
|
||||
{data?.queryTimeCost && isDeveloper && (
|
||||
<span className={`${prefixCls}-title-tip`}>(耗时: {data.queryTimeCost}ms)</span>
|
||||
)}
|
||||
</Col>
|
||||
<Col flex="0 1 70px">
|
||||
{[MsgContentTypeEnum.METRIC_TREND, MsgContentTypeEnum.METRIC_BAR].includes(
|
||||
msgContentType as MsgContentTypeEnum
|
||||
) && (
|
||||
<Switch
|
||||
checkedChildren="表格"
|
||||
unCheckedChildren="表格"
|
||||
onChange={checked => {
|
||||
setShowMsgContentTable(checked);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<div
|
||||
className={`${prefixCls}-step-title ${prefixCls}-execute-title-bar`}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<div>
|
||||
{titlePrefix}查询
|
||||
{!!data?.queryTimeCost && isDeveloper && (
|
||||
<span className={`${prefixCls}-title-tip`}>(耗时: {data.queryTimeCost}ms)</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{[MsgContentTypeEnum.METRIC_TREND, MsgContentTypeEnum.METRIC_BAR].includes(
|
||||
msgContentType as MsgContentTypeEnum
|
||||
) && (
|
||||
<Switch
|
||||
checkedChildren="表格"
|
||||
unCheckedChildren="表格"
|
||||
onChange={checked => {
|
||||
setShowMsgContentTable(checked);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!data?.queryColumns?.length && (
|
||||
<Button className={`${prefixCls}-export-data`} size="small" onClick={onExportData}>
|
||||
<DownloadOutlined />
|
||||
导出数据
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -118,7 +118,7 @@ const ParseTip: React.FC<Props> = ({
|
||||
return getNode(
|
||||
<>
|
||||
意图解析失败
|
||||
{parseTimeCost && isDeveloper && (
|
||||
{!!parseTimeCost && isDeveloper && (
|
||||
<span className={`${prefixCls}-title-tip`}>(耗时: {parseTimeCost}ms)</span>
|
||||
)}
|
||||
</>,
|
||||
@@ -338,7 +338,7 @@ const ParseTip: React.FC<Props> = ({
|
||||
<div className={`${prefixCls}-title-bar`}>
|
||||
<div>
|
||||
意图解析
|
||||
{parseTimeCost && isDeveloper && (
|
||||
{!!parseTimeCost && isDeveloper && (
|
||||
<span className={`${prefixCls}-title-tip`}>(耗时: {parseTimeCost}ms)</span>
|
||||
)}
|
||||
{parseInfoOptions?.length > 1 ? ':' : ''}
|
||||
|
||||
@@ -19,6 +19,7 @@ type Props = {
|
||||
queryMode?: string;
|
||||
sqlInfo: SqlInfoType;
|
||||
sqlTimeCost?: number;
|
||||
executeErrorMsg: string;
|
||||
};
|
||||
|
||||
const SqlItem: React.FC<Props> = ({
|
||||
@@ -31,6 +32,7 @@ const SqlItem: React.FC<Props> = ({
|
||||
queryMode,
|
||||
sqlInfo,
|
||||
sqlTimeCost,
|
||||
executeErrorMsg,
|
||||
}) => {
|
||||
const [sqlType, setSqlType] = useState('');
|
||||
|
||||
@@ -126,6 +128,14 @@ ${format(sqlInfo.querySQL)}
|
||||
`;
|
||||
};
|
||||
|
||||
const getErrorMsgText = () => {
|
||||
return `
|
||||
异常日志
|
||||
|
||||
${executeErrorMsg}
|
||||
`;
|
||||
};
|
||||
|
||||
const onExportLog = () => {
|
||||
let text = '';
|
||||
if (question) {
|
||||
@@ -148,6 +158,9 @@ ${format(sqlInfo.querySQL)}
|
||||
if (sqlInfo.querySQL) {
|
||||
text += getQuerySQLText();
|
||||
}
|
||||
if (!!executeErrorMsg) {
|
||||
text += getErrorMsgText();
|
||||
}
|
||||
exportTextFile(text, `supersonic-debug-${agentId}-${queryId}.log`);
|
||||
};
|
||||
|
||||
@@ -157,7 +170,7 @@ ${format(sqlInfo.querySQL)}
|
||||
<CheckCircleFilled className={`${tipPrefixCls}-step-icon`} />
|
||||
<div className={`${tipPrefixCls}-step-title`}>
|
||||
SQL生成
|
||||
{sqlTimeCost && (
|
||||
{!!sqlTimeCost && (
|
||||
<span className={`${tipPrefixCls}-title-tip`}>(耗时: {sqlTimeCost}ms)</span>
|
||||
)}
|
||||
:
|
||||
|
||||
@@ -73,6 +73,7 @@ const ChatItem: React.FC<Props> = ({
|
||||
const [executeMode, setExecuteMode] = useState(false);
|
||||
const [executeLoading, setExecuteLoading] = useState(false);
|
||||
const [executeTip, setExecuteTip] = useState('');
|
||||
const [executeErrorMsg, setExecuteErrorMsg] = useState('');
|
||||
const [data, setData] = useState<MsgDataType>();
|
||||
const [entitySwitchLoading, setEntitySwitchLoading] = useState(false);
|
||||
const [dimensionFilters, setDimensionFilters] = useState<FilterItemType[]>([]);
|
||||
@@ -87,8 +88,9 @@ const ChatItem: React.FC<Props> = ({
|
||||
const updateData = (res: Result<MsgDataType>) => {
|
||||
let tip: string = '';
|
||||
let data: MsgDataType | undefined = undefined;
|
||||
const { queryColumns, queryResults, queryState, queryMode, response, chatContext } =
|
||||
const { queryColumns, queryResults, queryState, queryMode, response, chatContext, errorMsg } =
|
||||
res.data || {};
|
||||
setExecuteErrorMsg(errorMsg);
|
||||
if (res.code === 400 || res.code === 401 || res.code === 412) {
|
||||
tip = res.msg;
|
||||
} else if (res.code !== 200) {
|
||||
@@ -366,6 +368,7 @@ const ChatItem: React.FC<Props> = ({
|
||||
queryMode={parseInfo.queryMode}
|
||||
sqlInfo={parseInfo.sqlInfo}
|
||||
sqlTimeCost={parseTimeCost?.sqlTime}
|
||||
executeErrorMsg={executeErrorMsg}
|
||||
/>
|
||||
)}
|
||||
<ExecuteItem
|
||||
|
||||
@@ -151,6 +151,12 @@
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
&-execute-title-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-reload {
|
||||
margin-left: 2px;
|
||||
width: fit-content;
|
||||
@@ -392,6 +398,14 @@
|
||||
border-left: 1px solid var(--green);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&-export-data {
|
||||
margin-left: 12px;
|
||||
width: fit-content;
|
||||
font-weight: normal;
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 13px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.@{filter-item-prefix-cls} {
|
||||
|
||||
@@ -303,3 +303,30 @@ export function exportTextFile(content: string, fileName: string, mimeType: stri
|
||||
// 释放 URL 对象
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function replacer(key: string, value: any) {
|
||||
return value === null ? '' : value; // 将null值转换为空字符串
|
||||
}
|
||||
|
||||
export function exportCsvFile(data: any[]) {
|
||||
// 生成CSV内容
|
||||
const csvRows: any[] = [];
|
||||
const headers = Object.keys(data[0]);
|
||||
csvRows.push(headers.join(',')); // 添加表头
|
||||
|
||||
for (const row of data) {
|
||||
csvRows.push(headers.map(header => JSON.stringify(row[header], replacer)).join(','));
|
||||
}
|
||||
|
||||
// 创建Blob并下载文件
|
||||
const csvString = '\ufeff' + csvRows.join('\n');
|
||||
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'data.csv'; // 指定下载文件名
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url); // 释放Blob URL
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user