mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-12 12:37:55 +00:00
(feature)(chat-sdk) add export log (#1482)
This commit is contained in:
@@ -143,19 +143,6 @@ const ParseTip: React.FC<Props> = ({
|
|||||||
|
|
||||||
const entityAlias = entity?.alias?.[0]?.split('.')?.[0];
|
const entityAlias = entity?.alias?.[0]?.split('.')?.[0];
|
||||||
|
|
||||||
const entityDimensions = entityInfo?.dimensions?.filter(
|
|
||||||
item =>
|
|
||||||
!['zyqk_song_id', 'song_name', 'singer_id', 'zyqk_cmpny_id'].includes(item.bizName) &&
|
|
||||||
!(
|
|
||||||
entityInfo?.dimensions?.some(dimension => dimension.bizName === 'singer_id') &&
|
|
||||||
item.bizName === 'singer_name'
|
|
||||||
) &&
|
|
||||||
!(
|
|
||||||
entityInfo?.dimensions?.some(dimension => dimension.bizName === 'zyqk_cmpny_id') &&
|
|
||||||
item.bizName === 'cmpny_name'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const getTipNode = () => {
|
const getTipNode = () => {
|
||||||
const dimensionItems = dimensions?.filter(item => item.type === 'DIMENSION');
|
const dimensionItems = dimensions?.filter(item => item.type === 'DIMENSION');
|
||||||
const itemValueClass = `${prefixCls}-tip-item-value`;
|
const itemValueClass = `${prefixCls}-tip-item-value`;
|
||||||
@@ -230,7 +217,7 @@ const ParseTip: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
{queryMode !== 'TAG_ID' &&
|
{queryMode !== 'TAG_ID' &&
|
||||||
!dimensions?.some(item => item.bizName?.includes('_id')) &&
|
!dimensions?.some(item => item.bizName?.includes('_id')) &&
|
||||||
entityDimensions
|
entityInfo?.dimensions
|
||||||
?.filter(dimension => dimension.value != null)
|
?.filter(dimension => dimension.value != null)
|
||||||
.map(dimension => (
|
.map(dimension => (
|
||||||
<div className={`${prefixCls}-tip-item`} key={dimension.itemId}>
|
<div className={`${prefixCls}-tip-item`} key={dimension.itemId}>
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { format } from 'sql-formatter';
|
|||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||||
import { solarizedlight } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
import { solarizedlight } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||||
import { message } from 'antd';
|
import { Button, message } from 'antd';
|
||||||
import { PREFIX_CLS } from '../../common/constants';
|
import { PREFIX_CLS } from '../../common/constants';
|
||||||
import { CheckCircleFilled, UpOutlined } from '@ant-design/icons';
|
import { CheckCircleFilled, DownloadOutlined, UpOutlined } from '@ant-design/icons';
|
||||||
import { SqlInfoType } from '../../common/type';
|
import { SqlInfoType } from '../../common/type';
|
||||||
|
import { exportTextFile } from '../../utils/utils';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
llmReq?: any;
|
llmReq?: any;
|
||||||
@@ -46,6 +47,99 @@ const SqlItem: React.FC<Props> = ({
|
|||||||
|
|
||||||
const fewShots = (Object.values(llmResp?.sqlRespMap || {})[0] as any)?.fewShots || [];
|
const fewShots = (Object.values(llmResp?.sqlRespMap || {})[0] as any)?.fewShots || [];
|
||||||
|
|
||||||
|
const getSchemaMapText = () => {
|
||||||
|
return `
|
||||||
|
Schema映射
|
||||||
|
${schema?.fieldNameList?.length > 0 ? `名称:${schema.fieldNameList.join('、')}` : ''}${
|
||||||
|
linking?.length > 0
|
||||||
|
? `
|
||||||
|
取值:${linking
|
||||||
|
.map((item: any) => {
|
||||||
|
return `${item.fieldName}: ${item.fieldValue}`;
|
||||||
|
})
|
||||||
|
.join('、')}`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
priorExts
|
||||||
|
? `
|
||||||
|
附加:${priorExts}`
|
||||||
|
: ''
|
||||||
|
}${
|
||||||
|
schema?.terms?.length > 0
|
||||||
|
? `
|
||||||
|
术语:${schema.terms
|
||||||
|
.map((item: any) => {
|
||||||
|
return `${item.name}${item.alias?.length > 0 ? `(${item.alias.join(',')})` : ''}: ${
|
||||||
|
item.description
|
||||||
|
}`;
|
||||||
|
})
|
||||||
|
.join('、')}`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFewShotText = () => {
|
||||||
|
return `
|
||||||
|
Few-shot示例${fewShots
|
||||||
|
.map((item: any, index: number) => {
|
||||||
|
return `
|
||||||
|
|
||||||
|
示例${index + 1}:
|
||||||
|
问题:${item.question}
|
||||||
|
SQL:
|
||||||
|
${format(item.sql)}
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.join('')}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getParsedS2SQLText = () => {
|
||||||
|
return `
|
||||||
|
${queryMode === 'LLM_S2SQL' || queryMode === 'PLAIN_TEXT' ? 'LLM' : 'Rule'}解析S2SQL
|
||||||
|
|
||||||
|
${format(sqlInfo.parsedS2SQL)}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCorrectedS2SQLText = () => {
|
||||||
|
return `
|
||||||
|
修正S2SQL
|
||||||
|
|
||||||
|
${format(sqlInfo.correctedS2SQL)}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getQuerySQLText = () => {
|
||||||
|
return `
|
||||||
|
最终执行SQL
|
||||||
|
|
||||||
|
${format(sqlInfo.querySQL)}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onExportLog = () => {
|
||||||
|
let text = '';
|
||||||
|
if (llmReq) {
|
||||||
|
text += getSchemaMapText();
|
||||||
|
}
|
||||||
|
if (fewShots.length > 0) {
|
||||||
|
text += getFewShotText();
|
||||||
|
}
|
||||||
|
if (sqlInfo.parsedS2SQL) {
|
||||||
|
text += getParsedS2SQLText();
|
||||||
|
}
|
||||||
|
if (sqlInfo.correctedS2SQL) {
|
||||||
|
text += getCorrectedS2SQLText();
|
||||||
|
}
|
||||||
|
if (sqlInfo.querySQL) {
|
||||||
|
text += getQuerySQLText();
|
||||||
|
}
|
||||||
|
exportTextFile(text, 'file.txt');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${tipPrefixCls}-parse-tip`}>
|
<div className={`${tipPrefixCls}-parse-tip`}>
|
||||||
<div className={`${tipPrefixCls}-title-bar`}>
|
<div className={`${tipPrefixCls}-title-bar`}>
|
||||||
@@ -123,6 +217,10 @@ const SqlItem: React.FC<Props> = ({
|
|||||||
最终执行SQL
|
最终执行SQL
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Button className={`${prefixCls}-export-log`} size="small" onClick={onExportLog}>
|
||||||
|
<DownloadOutlined />
|
||||||
|
导出日志
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
SimilarQuestionType,
|
SimilarQuestionType,
|
||||||
} from '../../common/type';
|
} from '../../common/type';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { chatExecute, chatParse, queryData, queryEntityInfo, switchEntity } from '../../service';
|
import { chatExecute, chatParse, queryData, 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 IconFont from '../IconFont';
|
import IconFont from '../IconFont';
|
||||||
import ParseTip from './ParseTip';
|
import ParseTip from './ParseTip';
|
||||||
@@ -292,19 +292,12 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEntityInfo = async (parseInfoValue: ChatContextType) => {
|
|
||||||
const res = await queryEntityInfo(parseInfoValue.queryId, parseInfoValue.id);
|
|
||||||
setEntityInfo(res.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSelectParseInfo = async (parseInfoValue: ChatContextType) => {
|
const onSelectParseInfo = async (parseInfoValue: ChatContextType) => {
|
||||||
setParseInfo(parseInfoValue);
|
setParseInfo(parseInfoValue);
|
||||||
updateDimensionFitlers(parseInfoValue.dimensionFilters || []);
|
updateDimensionFitlers(parseInfoValue.dimensionFilters || []);
|
||||||
setDateInfo(parseInfoValue.dateInfo);
|
setDateInfo(parseInfoValue.dateInfo);
|
||||||
if (parseInfoValue.entityInfo) {
|
if (parseInfoValue.entityInfo) {
|
||||||
setEntityInfo(parseInfoValue.entityInfo);
|
setEntityInfo(parseInfoValue.entityInfo);
|
||||||
} else {
|
|
||||||
getEntityInfo(parseInfoValue);
|
|
||||||
}
|
}
|
||||||
if (dataCache[parseInfoValue.id!]) {
|
if (dataCache[parseInfoValue.id!]) {
|
||||||
const { tip, data } = dataCache[parseInfoValue.id!];
|
const { tip, data } = dataCache[parseInfoValue.id!];
|
||||||
|
|||||||
@@ -541,6 +541,14 @@
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-export-log {
|
||||||
|
margin-left: 20px;
|
||||||
|
width: fit-content;
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
font-size: 13px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{sql-item-prefix-cls}-copilot {
|
.@{sql-item-prefix-cls}-copilot {
|
||||||
|
|||||||
@@ -131,9 +131,3 @@ export function queryDimensionValues(
|
|||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queryEntityInfo(queryId: number, parseId: number) {
|
|
||||||
return axios.get<EntityInfoType>(
|
|
||||||
`${prefix}/chat/query/getEntityInfo?queryId=${queryId}&parseId=${parseId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export const getFormattedValue = (value: number | string, remainZero?: boolean)
|
|||||||
let formattedValue = formatByUnit(value, unit);
|
let formattedValue = formatByUnit(value, unit);
|
||||||
formattedValue = formatByDecimalPlaces(
|
formattedValue = formatByDecimalPlaces(
|
||||||
formattedValue,
|
formattedValue,
|
||||||
unit === NumericUnit.OneHundredMillion ? 2 : +value < 1 ? 3 : 1,
|
unit === NumericUnit.OneHundredMillion ? 2 : +value < 1 ? 3 : 1
|
||||||
);
|
);
|
||||||
formattedValue = formatByThousandSeperator(formattedValue);
|
formattedValue = formatByThousandSeperator(formattedValue);
|
||||||
if ((typeof formattedValue === 'number' && isNaN(formattedValue)) || +formattedValue === 0) {
|
if ((typeof formattedValue === 'number' && isNaN(formattedValue)) || +formattedValue === 0) {
|
||||||
@@ -88,11 +88,11 @@ export const getFormattedValue = (value: number | string, remainZero?: boolean)
|
|||||||
export const formatNumberWithCN = (num: number) => {
|
export const formatNumberWithCN = (num: number) => {
|
||||||
if (isNaN(num)) return '-';
|
if (isNaN(num)) return '-';
|
||||||
if (num >= 10000) {
|
if (num >= 10000) {
|
||||||
return (num / 10000).toFixed(1) + "万";
|
return (num / 10000).toFixed(1) + '万';
|
||||||
} else {
|
} else {
|
||||||
return formatByDecimalPlaces(num, 2);
|
return formatByDecimalPlaces(num, 2);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const groupByColumn = (data: any[], column: string) => {
|
export const groupByColumn = (data: any[], column: string) => {
|
||||||
return data.reduce((result, item) => {
|
return data.reduce((result, item) => {
|
||||||
@@ -124,11 +124,15 @@ export const normalizeTrendData = (
|
|||||||
valueColumnName: string,
|
valueColumnName: string,
|
||||||
startDate: string,
|
startDate: string,
|
||||||
endDate: string,
|
endDate: string,
|
||||||
dateType?: string,
|
dateType?: string
|
||||||
) => {
|
) => {
|
||||||
const dateList = enumerateDaysBetweenDates(moment(startDate), moment(endDate), dateType);
|
const dateList = enumerateDaysBetweenDates(moment(startDate), moment(endDate), dateType);
|
||||||
const result = dateList.map((date) => {
|
const result = dateList.map(date => {
|
||||||
const item = resultList.find((result) => moment(result[dateColumnName]).format(dateType === 'months' ? 'YYYY-MM' : 'YYYY-MM-DD') === date);
|
const item = resultList.find(
|
||||||
|
result =>
|
||||||
|
moment(result[dateColumnName]).format(dateType === 'months' ? 'YYYY-MM' : 'YYYY-MM-DD') ===
|
||||||
|
date
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
...(item || {}),
|
...(item || {}),
|
||||||
[dateColumnName]: date,
|
[dateColumnName]: date,
|
||||||
@@ -139,7 +143,7 @@ export const normalizeTrendData = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getMinMaxDate = (resultList: any[], dateColumnName: string) => {
|
export const getMinMaxDate = (resultList: any[], dateColumnName: string) => {
|
||||||
const dateList = resultList.map((item) => moment(item[dateColumnName]));
|
const dateList = resultList.map(item => moment(item[dateColumnName]));
|
||||||
return [moment.min(dateList).format('YYYY-MM-DD'), moment.max(dateList).format('YYYY-MM-DD')];
|
return [moment.min(dateList).format('YYYY-MM-DD'), moment.max(dateList).format('YYYY-MM-DD')];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -177,7 +181,6 @@ export const isIOS = window.navigator.userAgent.match(/(iPhone|iPod|ios)/i);
|
|||||||
|
|
||||||
export const isAndroid = window.navigator.userAgent.match(/(Android)/i);
|
export const isAndroid = window.navigator.userAgent.match(/(Android)/i);
|
||||||
|
|
||||||
|
|
||||||
export function isProd() {
|
export function isProd() {
|
||||||
return process.env.NODE_ENV === 'production';
|
return process.env.NODE_ENV === 'production';
|
||||||
}
|
}
|
||||||
@@ -248,7 +251,7 @@ export const getTextWidth = (
|
|||||||
text: string,
|
text: string,
|
||||||
fontSize: string = '16px',
|
fontSize: string = '16px',
|
||||||
fontWeight: string = 'normal',
|
fontWeight: string = 'normal',
|
||||||
fontFamily: string = 'DINPro Medium',
|
fontFamily: string = 'DINPro Medium'
|
||||||
): number => {
|
): number => {
|
||||||
const canvas = utilCanvas || (utilCanvas = document.createElement('canvas'));
|
const canvas = utilCanvas || (utilCanvas = document.createElement('canvas'));
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
@@ -271,3 +274,32 @@ export function jsonParse(config: any, defaultReturn?: any) {
|
|||||||
return defaultReturn;
|
return defaultReturn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出文本文件的函数
|
||||||
|
* @param content - 要导出的文本内容
|
||||||
|
* @param fileName - 导出的文件名
|
||||||
|
* @param mimeType - 文件的 MIME 类型,默认为 'text/plain'
|
||||||
|
*/
|
||||||
|
export function exportTextFile(content: string, fileName: string, mimeType: string = 'text/plain') {
|
||||||
|
// 创建一个 Blob 对象
|
||||||
|
const blob = new Blob([content], { type: mimeType });
|
||||||
|
|
||||||
|
// 创建一个 URL 对象
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
// 创建一个 <a> 元素
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = fileName;
|
||||||
|
|
||||||
|
// 触发下载
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
// 移除 <a> 元素
|
||||||
|
document.body.removeChild(a);
|
||||||
|
|
||||||
|
// 释放 URL 对象
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user