mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-12 04:27:39 +00:00
355 lines
10 KiB
TypeScript
355 lines
10 KiB
TypeScript
import React, { useState } from 'react';
|
||
import { format } from 'sql-formatter';
|
||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||
import { solarizedlight } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||
import { Button, message } from 'antd';
|
||
import { PREFIX_CLS } from '../../common/constants';
|
||
import { CheckCircleFilled, DownloadOutlined, UpOutlined } from '@ant-design/icons';
|
||
import { SqlInfoType } from '../../common/type';
|
||
import { exportTextFile } from '../../utils/utils';
|
||
|
||
type Props = {
|
||
agentId?: number;
|
||
queryId: number;
|
||
question: string;
|
||
llmReq?: any;
|
||
llmResp?: any;
|
||
integrateSystem?: string;
|
||
queryMode?: string;
|
||
sqlInfo: SqlInfoType;
|
||
sqlTimeCost?: number;
|
||
executeErrorMsg: string;
|
||
};
|
||
|
||
const SqlItem: React.FC<Props> = ({
|
||
agentId,
|
||
queryId,
|
||
question,
|
||
llmReq,
|
||
llmResp,
|
||
integrateSystem,
|
||
queryMode,
|
||
sqlInfo,
|
||
sqlTimeCost,
|
||
executeErrorMsg,
|
||
}) => {
|
||
const [sqlType, setSqlType] = useState('');
|
||
|
||
const tipPrefixCls = `${PREFIX_CLS}-item`;
|
||
const prefixCls = `${PREFIX_CLS}-sql-item`;
|
||
|
||
const handleCopy = (_: string, result: any) => {
|
||
result ? message.success('复制SQL成功', 1) : message.error('复制SQL失败', 1);
|
||
};
|
||
|
||
const onCollapse = () => {
|
||
setSqlType('');
|
||
};
|
||
|
||
if (!llmReq && !sqlInfo.parsedS2SQL && !sqlInfo.correctedS2SQL && !sqlInfo.querySQL) {
|
||
return null;
|
||
}
|
||
|
||
const { schema, terms, priorExts } = llmReq || {};
|
||
|
||
const fewShots = (Object.values(llmResp?.sqlRespMap || {})[0] as any)?.fewShots || [];
|
||
|
||
const getSchemaMapText = () => {
|
||
return `
|
||
Schema映射
|
||
${schema?.fieldNameList?.length > 0 ? `名称:${schema.fieldNameList.join('、')}` : ''}${
|
||
schema?.values?.length > 0
|
||
? `
|
||
取值:${schema.values
|
||
.map((item: any) => {
|
||
return `${item.fieldName}: ${item.fieldValue}`;
|
||
})
|
||
.join('、')}`
|
||
: ''
|
||
}${
|
||
priorExts
|
||
? `
|
||
附加:${priorExts}`
|
||
: ''
|
||
}${
|
||
terms?.length > 0
|
||
? `
|
||
术语:${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 getErrorMsgText = () => {
|
||
return `
|
||
异常日志
|
||
|
||
${executeErrorMsg}
|
||
`;
|
||
};
|
||
|
||
const onExportLog = () => {
|
||
let text = '';
|
||
if (question) {
|
||
text += `
|
||
问题:${question}
|
||
`;
|
||
}
|
||
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();
|
||
}
|
||
if (!!executeErrorMsg) {
|
||
text += getErrorMsgText();
|
||
}
|
||
exportTextFile(text, `supersonic-debug-${agentId}-${queryId}.log`);
|
||
};
|
||
|
||
return (
|
||
<div className={`${tipPrefixCls}-parse-tip`}>
|
||
<div className={`${tipPrefixCls}-title-bar`}>
|
||
<CheckCircleFilled className={`${tipPrefixCls}-step-icon`} />
|
||
<div className={`${tipPrefixCls}-step-title`}>
|
||
SQL生成
|
||
{!!sqlTimeCost && (
|
||
<span className={`${tipPrefixCls}-title-tip`}>(耗时: {sqlTimeCost}ms)</span>
|
||
)}
|
||
:
|
||
{sqlType && (
|
||
<span className={`${prefixCls}-toggle-expand-btn`} onClick={onCollapse}>
|
||
<UpOutlined />
|
||
</span>
|
||
)}
|
||
</div>
|
||
<div className={`${tipPrefixCls}-content-options`}>
|
||
{llmReq && (
|
||
<div
|
||
className={`${tipPrefixCls}-content-option ${
|
||
sqlType === 'schemaMap' ? `${tipPrefixCls}-content-option-active` : ''
|
||
}`}
|
||
onClick={() => {
|
||
setSqlType(sqlType === 'schemaMap' ? '' : 'schemaMap');
|
||
}}
|
||
>
|
||
Schema映射
|
||
</div>
|
||
)}
|
||
{fewShots.length > 0 && (
|
||
<div
|
||
className={`${tipPrefixCls}-content-option ${
|
||
sqlType === 'fewShots' ? `${tipPrefixCls}-content-option-active` : ''
|
||
}`}
|
||
onClick={() => {
|
||
setSqlType(sqlType === 'fewShots' ? '' : 'fewShots');
|
||
}}
|
||
>
|
||
Few-shot示例
|
||
</div>
|
||
)}
|
||
{sqlInfo.parsedS2SQL && (
|
||
<div
|
||
className={`${tipPrefixCls}-content-option ${
|
||
sqlType === 'parsedS2SQL' ? `${tipPrefixCls}-content-option-active` : ''
|
||
}`}
|
||
onClick={() => {
|
||
setSqlType(sqlType === 'parsedS2SQL' ? '' : 'parsedS2SQL');
|
||
}}
|
||
>
|
||
{queryMode === 'LLM_S2SQL' || queryMode === 'PLAIN_TEXT' ? 'LLM' : 'Rule'}解析S2SQL
|
||
</div>
|
||
)}
|
||
{sqlInfo.correctedS2SQL && (
|
||
<div
|
||
className={`${tipPrefixCls}-content-option ${
|
||
sqlType === 'correctedS2SQL' ? `${tipPrefixCls}-content-option-active` : ''
|
||
}`}
|
||
onClick={() => {
|
||
setSqlType(sqlType === 'correctedS2SQL' ? '' : 'correctedS2SQL');
|
||
}}
|
||
>
|
||
修正S2SQL
|
||
</div>
|
||
)}
|
||
{sqlInfo.querySQL && (
|
||
<div
|
||
className={`${tipPrefixCls}-content-option ${
|
||
sqlType === 'querySQL' ? `${tipPrefixCls}-content-option-active` : ''
|
||
}`}
|
||
onClick={() => {
|
||
setSqlType(sqlType === 'querySQL' ? '' : 'querySQL');
|
||
}}
|
||
>
|
||
最终执行SQL
|
||
</div>
|
||
)}
|
||
<Button className={`${prefixCls}-export-log`} size="small" onClick={onExportLog}>
|
||
<DownloadOutlined />
|
||
导出日志
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
<div
|
||
className={`${prefixCls} ${
|
||
!window.location.pathname.includes('/chat') &&
|
||
integrateSystem &&
|
||
integrateSystem !== 'wiki'
|
||
? `${prefixCls}-copilot`
|
||
: ''
|
||
}`}
|
||
>
|
||
{sqlType === 'schemaMap' && (
|
||
<div className={`${prefixCls}-code`}>
|
||
{schema?.fieldNameList?.length > 0 && (
|
||
<div className={`${prefixCls}-schema-row`}>
|
||
<div className={`${prefixCls}-schema-title`}>名称:</div>
|
||
<div className={`${prefixCls}-schema-content`}>
|
||
{schema.fieldNameList.join('、')}
|
||
</div>
|
||
</div>
|
||
)}
|
||
{schema?.values?.length > 0 && (
|
||
<div className={`${prefixCls}-schema-row`}>
|
||
<div className={`${prefixCls}-schema-title`}>取值:</div>
|
||
<div className={`${prefixCls}-schema-content`}>
|
||
{schema.values
|
||
.map((item: any) => {
|
||
return `${item.fieldName}: ${item.fieldValue}`;
|
||
})
|
||
.join('、')}
|
||
</div>
|
||
</div>
|
||
)}
|
||
{priorExts && (
|
||
<div className={`${prefixCls}-schema-row`}>
|
||
<div className={`${prefixCls}-schema-title`}>附加:</div>
|
||
<div className={`${prefixCls}-schema-content`}>{priorExts}</div>
|
||
</div>
|
||
)}
|
||
{terms?.length > 0 && (
|
||
<div className={`${prefixCls}-schema-row`}>
|
||
<div className={`${prefixCls}-schema-title`}>术语:</div>
|
||
<div className={`${prefixCls}-schema-content`}>
|
||
{terms
|
||
.map((item: any) => {
|
||
return `${item.name}${
|
||
item.alias?.length > 0 ? `(${item.alias.join(',')})` : ''
|
||
}: ${item.description}`;
|
||
})
|
||
.join('、')}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
{sqlType === 'fewShots' && (
|
||
<div className={`${prefixCls}-code`}>
|
||
{fewShots.map((item: any, index: number) => {
|
||
return (
|
||
<div key={index} className={`${prefixCls}-few-shot-item`}>
|
||
<div className={`${prefixCls}-few-shot-title`}>示例{index + 1}:</div>
|
||
<div className={`${prefixCls}-few-shot-content`}>
|
||
<div className={`${prefixCls}-few-shot-content-item`}>
|
||
<div className={`${prefixCls}-few-shot-content-title`}>问题:</div>
|
||
<div className={`${prefixCls}-few-shot-content-text`}>{item.question}</div>
|
||
</div>
|
||
<div className={`${prefixCls}-few-shot-content-item`}>
|
||
<div className={`${prefixCls}-few-shot-content-title`}>SQL:</div>
|
||
<div className={`${prefixCls}-few-shot-content-text`}>
|
||
<SyntaxHighlighter
|
||
className={`${prefixCls}-few-shot-code`}
|
||
language="sql"
|
||
style={solarizedlight}
|
||
>
|
||
{item.sql}
|
||
</SyntaxHighlighter>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
)}
|
||
{sqlType && sqlInfo[sqlType] && (
|
||
<>
|
||
<SyntaxHighlighter
|
||
className={`${prefixCls}-code`}
|
||
language="sql"
|
||
style={solarizedlight}
|
||
>
|
||
{format(sqlInfo[sqlType])}
|
||
</SyntaxHighlighter>
|
||
<CopyToClipboard
|
||
text={format(sqlInfo[sqlType])}
|
||
onCopy={(text, result) => handleCopy(text, result)}
|
||
>
|
||
<button className={`${prefixCls}-copy-btn`}>复制代码</button>
|
||
</CopyToClipboard>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default SqlItem;
|