Integrate Chat and Copilot into chat-sdk, and add SQL parse display (#166)

This commit is contained in:
williamhliu
2023-10-02 18:05:12 +08:00
committed by GitHub
parent 741ed4191b
commit 71cb20eb4f
68 changed files with 1353 additions and 882 deletions

View File

@@ -58,7 +58,7 @@ const ExecuteItem: React.FC<Props> = ({
<>
<div className={`${prefixCls}-title-bar`}>
<CheckCircleFilled className={`${prefixCls}-step-icon`} />
<div className={`${prefixCls}-step-title`}></div>
<div className={`${prefixCls}-step-title`}></div>
</div>
<div className={`${prefixCls}-content-container ${prefixCls}-last-node`}>
<Spin spinning={entitySwitchLoading}>
@@ -68,7 +68,12 @@ const ExecuteItem: React.FC<Props> = ({
{data?.queryMode === 'WEB_PAGE' ? (
<WebPage id={queryId!} data={data} />
) : (
<ChatMsg data={data} chartIndex={chartIndex} triggerResize={triggerResize} />
<ChatMsg
queryId={queryId}
data={data}
chartIndex={chartIndex}
triggerResize={triggerResize}
/>
)}
</Spin>
</div>

View File

@@ -23,7 +23,7 @@ const FilterItem: React.FC<Props> = ({ modelId, filters, filter, onFiltersChange
const initData = async () => {
const { data } = await queryDimensionValues(modelId, filter.bizName, '');
setOptions(
data?.data?.resultList.map((item: any) => ({
data?.resultList.map((item: any) => ({
label: item[filter.bizName],
value: item[filter.bizName],
})) || []
@@ -47,9 +47,8 @@ const FilterItem: React.FC<Props> = ({ modelId, filters, filter, onFiltersChange
if (fetchId !== fetchRef.current) {
return;
}
setOptions(
newOptions.data?.data?.resultList.map((item: any) => ({
newOptions.data?.resultList.map((item: any) => ({
label: item[filter.bizName],
value: item[filter.bizName],
})) || []
@@ -76,7 +75,8 @@ const FilterItem: React.FC<Props> = ({ modelId, filters, filter, onFiltersChange
return (
<span className={prefixCls}>
{typeof filter.value === 'string' || isArray(filter.value) ? (
{(typeof filter.value === 'string' || isArray(filter.value)) &&
(filter.operator === '=' || filter.operator === 'IN') ? (
<Select
bordered={false}
value={filter.value}

View File

@@ -1,7 +1,7 @@
import React, { ReactNode } from 'react';
import { AGG_TYPE_MAP, PREFIX_CLS } from '../../common/constants';
import { ChatContextType, FilterItemType } from '../../common/type';
import { CheckCircleFilled, InfoCircleOutlined } from '@ant-design/icons';
import { CheckCircleFilled } from '@ant-design/icons';
import classNames from 'classnames';
import SwicthEntity from './SwitchEntity';
import Loading from './Loading';
@@ -30,7 +30,7 @@ const ParseTip: React.FC<Props> = ({
}) => {
const prefixCls = `${PREFIX_CLS}-item`;
const getNode = (tipTitle: string, tipNode?: ReactNode, parseSucceed?: boolean) => {
const getNode = (tipTitle: ReactNode, tipNode?: ReactNode, parseSucceed?: boolean) => {
const contentContainerClass = classNames(`${prefixCls}-content-container`, {
[`${prefixCls}-content-container-succeed`]: parseSucceed,
});
@@ -62,7 +62,6 @@ const ParseTip: React.FC<Props> = ({
const getTipNode = (parseInfo: ChatContextType, isOptions?: boolean, index?: number) => {
const {
modelId,
modelName,
dateInfo,
dimensionFilters,
@@ -76,8 +75,6 @@ const ParseTip: React.FC<Props> = ({
nativeQuery,
} = parseInfo || {};
const maxOptionCount = queryMode === 'DSL' ? 10 : MAX_OPTION_VALUES_COUNT;
const { startDate, endDate } = dateInfo || {};
const dimensionItems = dimensions?.filter(item => item.type === 'DIMENSION');
const metric = metrics?.[0];
@@ -92,44 +89,6 @@ const ParseTip: React.FC<Props> = ({
const fields =
queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems;
const getFilterContent = (filters: any) => {
return (
<div className={`${prefixCls}-tip-item-filter-content`}>
{filters.map((filter: any) => (
<div className={`${prefixCls}-tip-item-option`}>
<span>
<span className={`${prefixCls}-tip-item-filter-name`}>{filter.name}</span>
{filter.operator !== '=' && filter.operator !== 'IN'
? ` ${filter.operator} `
: ''}
</span>
{queryMode !== 'DSL' && !filter.bizName?.includes('_id') ? (
<FilterItem
modelId={modelId}
filters={dimensionFilters}
filter={filter}
onFiltersChange={onFiltersChange}
/>
) : (
<span className={itemValueClass}>{filter.value}</span>
)}
</div>
))}
</div>
);
};
const getFiltersNode = () => {
return (
<div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div>
<div className={`${prefixCls}-tip-item-content`}>
{getFilterContent(dimensionFilters)}
</div>
</div>
);
};
return (
<div
className={`${prefixCls}-tip-content`}
@@ -147,7 +106,7 @@ const ParseTip: React.FC<Props> = ({
</div>
) : (
<>
{(queryMode.includes('ENTITY') || queryMode === 'DSL') &&
{(queryMode?.includes('ENTITY') || queryMode === 'DSL') &&
typeof entityId === 'string' &&
!!entityAlias &&
!!entityName ? (
@@ -165,11 +124,11 @@ const ParseTip: React.FC<Props> = ({
</div>
) : (
<div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div>
<div className={`${prefixCls}-tip-item-name`}></div>
<div className={itemValueClass}>{modelName}</div>
</div>
)}
{!queryMode.includes('ENTITY') && metric && (
{!queryMode?.includes('ENTITY') && metric && (
<div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div>
<div className={itemValueClass}>{metric.name}</div>
@@ -199,24 +158,21 @@ const ParseTip: React.FC<Props> = ({
</div>
<div className={itemValueClass}>
{fields
.slice(0, maxOptionCount)
.slice(0, MAX_OPTION_VALUES_COUNT)
.map(field => field.name)
.join('、')}
{fields.length > maxOptionCount && '...'}
{fields.length > MAX_OPTION_VALUES_COUNT && '...'}
</div>
</div>
)}
{[
'METRIC_FILTER',
'METRIC_ENTITY',
'ENTITY_DETAIL',
'ENTITY_LIST_FILTER',
'ENTITY_ID',
'DSL',
].includes(queryMode) &&
dimensionFilters &&
dimensionFilters?.length > 0 &&
getFiltersNode()}
{queryMode !== 'ENTITY_ID' &&
entityDimensions?.length > 0 &&
entityDimensions.map(dimension => (
<div className={`${prefixCls}-tip-item`} key={dimension.itemId}>
<div className={`${prefixCls}-tip-item-name`}>{dimension.name}</div>
<div className={itemValueClass}>{dimension.value}</div>
</div>
))}
{queryMode === 'METRIC_ORDERBY' && aggType && aggType !== 'NONE' && (
<div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div>
@@ -230,7 +186,8 @@ const ParseTip: React.FC<Props> = ({
};
const parseInfo = parseInfoOptions[0] || {};
const { properties, entity, entityInfo, elementMatches, queryMode } = parseInfo || {};
const { modelId, properties, entity, entityInfo, elementMatches, queryMode, dimensionFilters } =
parseInfo || {};
const { type } = properties || {};
const entityAlias = entity?.alias?.[0]?.split('.')?.[0];
@@ -249,31 +206,71 @@ const ParseTip: React.FC<Props> = ({
)
);
const getFilterContent = (filters: any) => {
const itemValueClass = `${prefixCls}-tip-item-value`;
return (
<div className={`${prefixCls}-tip-item-filter-content`}>
{filters.map((filter: any) => (
<div className={`${prefixCls}-tip-item-option`} key={filter.name}>
<span>
<span className={`${prefixCls}-tip-item-filter-name`}>{filter.name}</span>
{filter.operator !== '=' && filter.operator !== 'IN' ? ` ${filter.operator} ` : ''}
</span>
{/* {queryMode !== 'DSL' && !filter.bizName?.includes('_id') ? ( */}
{!filter.bizName?.includes('_id') ? (
<FilterItem
modelId={modelId}
filters={dimensionFilters}
filter={filter}
onFiltersChange={onFiltersChange}
/>
) : (
<span className={itemValueClass}>{filter.value}</span>
)}
</div>
))}
</div>
);
};
const getFiltersNode = () => {
return (
<div className={`${prefixCls}-tip-item`}>
<div className={`${prefixCls}-tip-item-name`}></div>
<div className={`${prefixCls}-tip-item-content`}>{getFilterContent(dimensionFilters)}</div>
</div>
);
};
const tipNode = (
<div className={`${prefixCls}-tip`}>
{getTipNode(parseInfo)}
{queryMode !== 'ENTITY_ID' && entityDimensions?.length > 0 && (
<div className={`${prefixCls}-entity-info`}>
{entityDimensions.map(dimension => (
<div className={`${prefixCls}-dimension-item`} key={dimension.itemId}>
<div className={`${prefixCls}-dimension-name`}>{dimension.name}</div>
<div className={`${prefixCls}-dimension-value`}>{dimension.value}</div>
</div>
))}
</div>
)}
{(!type || queryMode === 'DSL') && entityAlias && entityName && (
<div className={`${prefixCls}-switch-entity-tip`}>
<InfoCircleOutlined />
<div>
{entityAlias}{entityAlias}
</div>
</div>
)}
{[
'METRIC_FILTER',
'METRIC_ENTITY',
'ENTITY_DETAIL',
'ENTITY_LIST_FILTER',
'ENTITY_ID',
'DSL',
].includes(queryMode) &&
dimensionFilters &&
dimensionFilters?.length > 0 &&
getFiltersNode()}
</div>
);
return getNode('意图解析结果', tipNode, true);
return getNode(
<div className={`${prefixCls}-title-bar`}>
{(!type || queryMode === 'DSL') && entityAlias && entityAlias !== '厂牌' && entityName && (
<div className={`${prefixCls}-switch-entity-tip`}>
({entityAlias}{entityAlias})
</div>
)}
</div>,
tipNode,
true
);
};
export default ParseTip;

View File

@@ -0,0 +1,60 @@
import { CheckCircleFilled, DownOutlined, UpOutlined } from '@ant-design/icons';
import { PREFIX_CLS } from '../../common/constants';
import { SimilarQuestionType } from '../../common/type';
import { useState } from 'react';
type Props = {
similarQuestions: SimilarQuestionType[];
defaultExpanded?: boolean;
onSelectQuestion: (question: SimilarQuestionType) => void;
};
const SimilarQuestions: React.FC<Props> = ({
similarQuestions,
defaultExpanded,
onSelectQuestion,
}) => {
const [expanded, setExpanded] = useState(defaultExpanded || false);
const tipPrefixCls = `${PREFIX_CLS}-item`;
const prefixCls = `${PREFIX_CLS}-similar-questions`;
const onToggleExpanded = () => {
setExpanded(!expanded);
};
return (
<div className={`${tipPrefixCls}-parse-tip`}>
<div className={`${tipPrefixCls}-title-bar`}>
<CheckCircleFilled className={`${tipPrefixCls}-step-icon`} />
<div className={`${tipPrefixCls}-step-title`}>
<span className={`${prefixCls}-toggle-expand-btn`} onClick={onToggleExpanded}>
{expanded ? <UpOutlined /> : <DownOutlined />}
</span>
</div>
</div>
<div className={prefixCls}>
{expanded && (
<div className={`${prefixCls}-content`}>
{similarQuestions.slice(0, 5).map((question, index) => {
return (
<div
className={`${prefixCls}-question`}
key={question.queryText}
onClick={() => {
onSelectQuestion(question);
}}
>
{index + 1}. {question.queryText}
</div>
);
})}
</div>
)}
</div>
</div>
);
};
export default SimilarQuestions;

View File

@@ -0,0 +1,116 @@
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 { message } from 'antd';
import { PREFIX_CLS } from '../../common/constants';
import { CheckCircleFilled, UpOutlined } from '@ant-design/icons';
import { SqlInfoType } from '../../common/type';
type Props = {
integrateSystem?: string;
sqlInfo: SqlInfoType;
};
const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => {
const [sqlType, setSqlType] = useState('');
const tipPrefixCls = `${PREFIX_CLS}-item`;
const prefixCls = `${PREFIX_CLS}-sql-item`;
const handleCopy = (text, result) => {
result ? message.success('复制SQL成功', 1) : message.error('复制SQL失败', 1);
};
const onCollapse = () => {
setSqlType('');
};
if (!sqlInfo.llmParseSql && !sqlInfo.logicSql && !sqlInfo.querySql) {
return null;
}
return (
<div className={`${tipPrefixCls}-parse-tip`}>
<div className={`${tipPrefixCls}-title-bar`}>
<CheckCircleFilled className={`${tipPrefixCls}-step-icon`} />
<div className={`${tipPrefixCls}-step-title`}>
SQL生成
{sqlType && (
<span className={`${prefixCls}-toggle-expand-btn`} onClick={onCollapse}>
<UpOutlined />
</span>
)}
</div>
<div className={`${prefixCls}-sql-options`}>
{sqlInfo.llmParseSql && (
<div
className={`${prefixCls}-sql-option ${
sqlType === 'llmParseSql' ? `${prefixCls}-sql-option-active` : ''
}`}
onClick={() => {
setSqlType(sqlType === 'llmParseSql' ? '' : 'llmParseSql');
}}
>
LLM解析SQL
</div>
)}
{sqlInfo.logicSql && (
<div
className={`${prefixCls}-sql-option ${
sqlType === 'logicSql' ? `${prefixCls}-sql-option-active` : ''
}`}
onClick={() => {
setSqlType(sqlType === 'logicSql' ? '' : 'logicSql');
}}
>
SQL
</div>
)}
{sqlInfo.querySql && (
<div
className={`${prefixCls}-sql-option ${
sqlType === 'querySql' ? `${prefixCls}-sql-option-active` : ''
}`}
onClick={() => {
setSqlType(sqlType === 'querySql' ? '' : 'querySql');
}}
>
SQL
</div>
)}
</div>
</div>
<div
className={`${prefixCls} ${
!window.location.pathname.includes('/chat') &&
integrateSystem &&
integrateSystem !== 'wiki'
? `${prefixCls}-copilot`
: ''
}`}
>
{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;

View File

@@ -1,4 +1,10 @@
import { ChatContextType, FilterItemType, MsgDataType, ParseStateEnum } from '../../common/type';
import {
ChatContextType,
FilterItemType,
MsgDataType,
ParseStateEnum,
SimilarQuestionType,
} from '../../common/type';
import { useEffect, useState } from 'react';
import { chatExecute, chatParse, queryData, switchEntity } from '../../service';
import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/constants';
@@ -8,6 +14,8 @@ import ExecuteItem from './ExecuteItem';
import { isMobile } from '../../utils/utils';
import classNames from 'classnames';
import Tools from '../Tools';
import SqlItem from './SqlItem';
import SimilarQuestionItem from './SimilarQuestionItem';
type Props = {
msg: string;
@@ -17,11 +25,13 @@ type Props = {
filter?: any[];
isLastMessage?: boolean;
msgData?: MsgDataType;
isHistory?: boolean;
triggerResize?: boolean;
parseOptions?: ChatContextType[];
isDeveloper?: boolean;
integrateSystem?: string;
onMsgDataLoaded?: (data: MsgDataType, valid: boolean) => void;
onUpdateMessageScroll?: () => void;
onSendMsg?: (msg: string) => void;
};
const ChatItem: React.FC<Props> = ({
@@ -31,12 +41,14 @@ const ChatItem: React.FC<Props> = ({
agentId,
filter,
isLastMessage,
isHistory,
triggerResize,
msgData,
parseOptions,
isDeveloper,
integrateSystem,
onMsgDataLoaded,
onUpdateMessageScroll,
onSendMsg,
}) => {
const [data, setData] = useState<MsgDataType>();
const [parseLoading, setParseLoading] = useState(false);
@@ -47,6 +59,7 @@ const ChatItem: React.FC<Props> = ({
const [executeTip, setExecuteTip] = useState('');
const [executeMode, setExecuteMode] = useState(false);
const [entitySwitchLoading, setEntitySwitchLoading] = useState(false);
const [similarQuestions, setSimilarQuestions] = useState<SimilarQuestionType[]>([]);
const [chartIndex, setChartIndex] = useState(0);
@@ -79,13 +92,13 @@ const ChatItem: React.FC<Props> = ({
setExecuteMode(true);
setExecuteLoading(true);
try {
const { data } = await chatExecute(msg, conversationId!, parseInfoValue);
const res: any = await chatExecute(msg, conversationId!, parseInfoValue);
setExecuteLoading(false);
const valid = updateData(data);
const valid = updateData(res);
if (onMsgDataLoaded) {
onMsgDataLoaded(
{
...data.data,
...res.data,
chatContext: parseInfoValue,
},
valid
@@ -97,12 +110,13 @@ const ChatItem: React.FC<Props> = ({
}
};
const onSendMsg = async () => {
const sendMsg = async () => {
setParseLoading(true);
const { data: parseData } = await chatParse(msg, conversationId, modelId, agentId, filter);
const parseData: any = await chatParse(msg, conversationId, modelId, agentId, filter);
setParseLoading(false);
const { code, data } = parseData || {};
const { state, selectedParses, queryId } = data || {};
const { state, selectedParses, queryId, similarSolvedQuery } = data || {};
setSimilarQuestions(similarSolvedQuery || []);
if (
code !== 200 ||
state === ParseStateEnum.FAILED ||
@@ -115,7 +129,7 @@ const ChatItem: React.FC<Props> = ({
if (onUpdateMessageScroll) {
onUpdateMessageScroll();
}
const parseInfos = selectedParses.map(item => ({
const parseInfos = selectedParses.map((item: any) => ({
...item,
queryId,
}));
@@ -126,15 +140,17 @@ const ChatItem: React.FC<Props> = ({
};
useEffect(() => {
if (data !== undefined || parseOptions !== undefined || executeTip !== '') {
if (data !== undefined || parseOptions !== undefined || executeTip !== '' || parseLoading) {
return;
}
if (msgData) {
setParseInfoOptions([msgData.chatContext]);
const parseInfoValue = { ...msgData.chatContext, queryId: msgData.queryId };
setParseInfoOptions([parseInfoValue]);
setParseInfo(parseInfoValue);
setExecuteMode(true);
updateData({ code: 200, data: msgData, msg: 'success' });
} else if (msg) {
onSendMsg();
sendMsg();
}
}, [msg, msgData]);
@@ -142,19 +158,27 @@ const ChatItem: React.FC<Props> = ({
setEntitySwitchLoading(true);
const res = await switchEntity(entityId, data?.chatContext?.modelId, conversationId || 0);
setEntitySwitchLoading(false);
setData(res.data.data);
const { chatContext } = res.data.data;
setData(res.data);
const { chatContext } = res.data;
setParseInfo(chatContext);
setParseInfoOptions([chatContext]);
};
const onFiltersChange = async (dimensionFilters: FilterItemType[]) => {
setEntitySwitchLoading(true);
const chatContextValue = { ...(parseInfoOptions[0] || {}), dimensionFilters };
const { dimensions, metrics, dateInfo, id, queryId } = parseInfoOptions[0] || {};
const chatContextValue = {
dimensions,
metrics,
dateInfo,
dimensionFilters,
parseId: id,
queryId,
};
const res: any = await queryData(chatContextValue);
setEntitySwitchLoading(false);
const resChatContext = res.data?.data?.chatContext;
setData({ ...(res.data?.data || {}), chatContext: resChatContext || chatContextValue });
const resChatContext = res.data?.chatContext;
setData({ ...(res.data || {}), chatContext: resChatContext || chatContextValue });
setParseInfo(resChatContext || chatContextValue);
setParseInfoOptions([resChatContext || chatContextValue]);
};
@@ -167,6 +191,10 @@ const ChatItem: React.FC<Props> = ({
}
};
const onSelectQuestion = (question: SimilarQuestionType) => {
onSendMsg?.(question.queryText);
};
const contentClass = classNames(`${prefixCls}-content`, {
[`${prefixCls}-content-mobile`]: isMobile,
});
@@ -189,17 +217,36 @@ const ChatItem: React.FC<Props> = ({
onSwitchEntity={onSwitchEntity}
onFiltersChange={onFiltersChange}
/>
{executeMode && (
<ExecuteItem
queryId={parseInfo?.queryId}
executeLoading={executeLoading}
entitySwitchLoading={entitySwitchLoading}
executeTip={executeTip}
chartIndex={chartIndex}
data={data}
triggerResize={triggerResize}
{parseTip && similarQuestions.length > 0 && (
<SimilarQuestionItem
similarQuestions={similarQuestions}
defaultExpanded
onSelectQuestion={onSelectQuestion}
/>
)}
{executeMode && (
<>
{parseInfoOptions?.[0]?.sqlInfo && isDeveloper && integrateSystem !== 'c2' && (
<SqlItem integrateSystem={integrateSystem} sqlInfo={parseInfoOptions[0].sqlInfo} />
)}
{similarQuestions.length > 0 && (
<SimilarQuestionItem
similarQuestions={similarQuestions}
defaultExpanded={executeTip !== ''}
onSelectQuestion={onSelectQuestion}
/>
)}
<ExecuteItem
queryId={parseInfo?.queryId}
executeLoading={executeLoading}
entitySwitchLoading={entitySwitchLoading}
executeTip={executeTip}
chartIndex={chartIndex}
data={data}
triggerResize={triggerResize}
/>
</>
)}
</div>
{!isMetricCard && data && (
<Tools data={data} scoreValue={undefined} isLastMessage={isLastMessage} />

View File

@@ -2,6 +2,8 @@
@chat-item-prefix-cls: ~'@{supersonic-chat-prefix}-item';
@filter-item-prefix-cls: ~'@{supersonic-chat-prefix}-filter-item';
@sql-item-prefix-cls: ~'@{supersonic-chat-prefix}-sql-item';
@similar-questions-prefix-cls: ~'@{supersonic-chat-prefix}-similar-questions';
.@{chat-item-prefix-cls} {
display: flex;
@@ -17,7 +19,6 @@
display: inline-block;
width: 4px;
height: 4px;
// border-radius: 50%;
background-color: var(--text-color);
margin: 0 2px;
opacity: 0;
@@ -124,8 +125,7 @@
display: flex;
align-items: center;
column-gap: 6px;
margin-top: 4px;
color: var(--text-color-third);
color: var(--text-color-fourth);
font-size: 13px;
}
@@ -169,7 +169,7 @@
&-tip {
display: flex;
flex-direction: column;
row-gap: 6px;
row-gap: 10px;
flex-wrap: wrap;
color: var(--text-color-third);
}
@@ -178,7 +178,7 @@
display: flex;
align-items: center;
flex-wrap: wrap;
row-gap: 6px;
row-gap: 10px;
column-gap: 12px;
color: var(--text-color-third);
}
@@ -251,7 +251,6 @@
flex-wrap: wrap;
row-gap: 6px;
column-gap: 12px;
margin-top: 4px;
color: var(--text-color-third);
font-size: 14px;
}
@@ -325,4 +324,93 @@
color: var(--chat-blue);
font-weight: 500;
}
}
}
.@{sql-item-prefix-cls} {
position: relative;
margin: 2px 0 2px 7px;
padding: 2px 0 8px 18px;
border-left: 1px solid var(--green);
overflow: auto;
&-toggle-expand-btn {
margin-left: 4px;
color: var(--text-color-fourth);
font-size: 12px;
cursor: pointer;
}
&-sql-options {
margin-left: 4px;
display: flex;
align-items: center;
column-gap: 13px;
color: var(--text-color-third);
}
&-sql-option {
border-radius: 4px;
padding: 1px 4px;
cursor: pointer;
&:hover {
color: var(--chat-blue);
}
}
&-sql-option-active {
color: #fff !important;
background-color: var(--chat-blue);
}
&-code {
margin-top: 10px !important;
padding: 6px 14px 8px !important;
border: 1px solid var(--border-color-base) !important;
border-radius: 4px !important;
background: #f5f8fb !important;
}
&-copy-btn {
position: absolute;
top: 30px;
right: 20px;
background: transparent !important;
border: 0 !important;
color: var(--chat-blue);
cursor: pointer;
}
}
.@{sql-item-prefix-cls}-copilot {
width: 700px;
}
.@{similar-questions-prefix-cls} {
position: relative;
margin: 2px 0 2px 7px;
padding: 2px 0 8px 18px;
border-left: 1px solid var(--green);
overflow: auto;
&-toggle-expand-btn {
margin-left: 4px;
color: var(--text-color-fourth);
font-size: 12px;
cursor: pointer;
}
&-content {
display: flex;
flex-direction: column;
row-gap: 12px;
margin-top: 6px;
margin-bottom: 2px;
}
&-question {
width: fit-content;
color: var(--chat-blue);
cursor: pointer;
}
}

View File

@@ -68,7 +68,7 @@ const Message: React.FC<Props> = ({
<div className={`${prefixCls}-info-bar`}>
<div className={`${prefixCls}-main-entity-info`}>
<div className={`${prefixCls}-info-item`}>
<div className={`${prefixCls}-info-name`}></div>
<div className={`${prefixCls}-info-name`}></div>
<div className={`${prefixCls}-info-value`}>{modelName}</div>
</div>
<div className={`${prefixCls}-info-item`}>

View File

@@ -19,33 +19,7 @@ const Text: React.FC<Props> = ({ columns, referenceColumn, dataSource }) => {
const initData = () => {
let textValue = dataSource[0][columns[0].nameEn];
let htmlCodeValue: string;
const match = textValue.match(/```html([\s\S]*?)```/);
htmlCodeValue = match && match[1].trim();
if (htmlCodeValue) {
textValue = textValue.replace(/```html([\s\S]*?)```/, '');
}
let scriptCode: string;
let scriptSrc: string;
if (htmlCodeValue) {
scriptSrc = htmlCodeValue.match(/<script src="([\s\S]*?)"><\/script>/)?.[1] || '';
scriptCode =
htmlCodeValue.match(/<script type="text\/javascript">([\s\S]*?)<\/script>/)?.[1] || '';
if (scriptSrc) {
const script = document.createElement('script');
script.src = scriptSrc;
document.body.appendChild(script);
}
if (scriptCode) {
const script = document.createElement('script');
script.innerHTML = scriptCode;
setTimeout(() => {
document.body.appendChild(script);
}, 1500);
}
}
setText(textValue);
setHtmlCode(htmlCodeValue);
setText(textValue === undefined ? '暂无数据,如有疑问请联系管理员' : textValue);
if (referenceColumn) {
const referenceDataValue = dataSource[0][referenceColumn.nameEn];
setReferenceData(referenceDataValue || []);

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { CLS_PREFIX } from '../../../common/constants';
import { MsgDataType } from '../../../common/type';
import { isProd } from '../../../utils/utils';
import { getToken, isProd } from '../../../utils/utils';
type Props = {
id: string | number;
@@ -89,10 +89,8 @@ const WebPage: React.FC<Props> = ({ id, data }) => {
);
urlValue = urlValue.replace(
'?',
`?miniProgram=true&reportName=${name}&filterData=${filterData}&`
`?token=${getToken()}&miniProgram=true&reportName=${name}&filterData=${filterData}&`
);
urlValue =
!isProd() && !urlValue.includes('http') ? `http://s2.tmeoa.com${urlValue}` : urlValue;
} else {
const params = Object.keys(valueParams || {}).map(key => `${key}=${valueParams[key]}`);
if (params.length > 0) {
@@ -103,7 +101,6 @@ const WebPage: React.FC<Props> = ({ id, data }) => {
}
}
}
// onReportLoaded(heightValue + 190);
setPluginUrl(urlValue);
};
@@ -112,7 +109,6 @@ const WebPage: React.FC<Props> = ({ id, data }) => {
}, []);
return (
// <div className={prefixCls} style={{ height }}>
<iframe
id={`reportIframe_${id}`}
name={`reportIframe_${id}`}
@@ -121,7 +117,6 @@ const WebPage: React.FC<Props> = ({ id, data }) => {
title="reportIframe"
allowFullScreen
/>
// </div>
);
};

View File

@@ -12,12 +12,13 @@ import DrillDownDimensions from '../DrillDownDimensions';
import MetricOptions from '../MetricOptions';
type Props = {
queryId?: number;
data: MsgDataType;
chartIndex: number;
triggerResize?: boolean;
};
const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize }) => {
const { queryColumns, queryResults, chatContext, queryMode } = data || {};
const { dimensionFilters, elementMatches } = chatContext || {};
@@ -127,14 +128,16 @@ const ChatMsg: React.FC<Props> = ({ data, chartIndex, triggerResize }) => {
const onLoadData = async (value: any) => {
setLoading(true);
const { data } = await queryData({
...chatContext,
const res: any = await queryData({
// ...chatContext,
queryId,
parseId: chatContext.id,
...value,
});
setLoading(false);
if (data.code === 200) {
updateColummns(data.data?.queryColumns || []);
setDataSource(data.data?.queryResults || []);
if (res.code === 200) {
updateColummns(res.data?.queryColumns || []);
setDataSource(res.data?.queryResults || []);
}
};

View File

@@ -34,7 +34,7 @@ const DrillDownDimensions: React.FC<Props> = ({
const initData = async () => {
const res = await queryDrillDownDimensions(modelId);
setDimensions(
res.data.data.dimensions
res.data.dimensions
.filter(
dimension =>
!dimensionFilters?.some(filter => filter.name === dimension.name) &&

View File

@@ -1,7 +1,7 @@
import { createFromIconfontCN } from '@ant-design/icons';
const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/c/font_4120566_x5c4www9bqm.js',
scriptUrl: '//at.alicdn.com/t/c/font_4120566_46xw04fpzii.js',
});
export default IconFont;

View File

@@ -25,7 +25,7 @@ const RecommendOptions: React.FC<Props> = ({ entityId, modelId, modelName, onSel
setLoading(true);
const res = await queryEntities(entityId, modelId);
setLoading(false);
setData(res.data.data);
setData(res.data);
};
useEffect(() => {