mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-14 22:25:19 +00:00
[feature](weaapp) add agent
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "supersonic-chat-sdk",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.32",
|
||||
"main": "dist/index.es.js",
|
||||
"module": "dist/index.es.js",
|
||||
"unpkg": "dist/index.umd.js",
|
||||
|
||||
@@ -40,13 +40,13 @@ export const THEME_COLOR_LIST = [
|
||||
'#5CA9E6',
|
||||
];
|
||||
|
||||
export const PARSE_ERROR_TIP = '小Q不太懂您说什么呐,回去一定补充知识';
|
||||
export const PARSE_ERROR_TIP = '智能助理不太懂您说什么呐,回去一定补充知识';
|
||||
|
||||
export const SEARCH_EXCEPTION_TIP = '查询出错啦,智能小Q还不够聪明,请您换个表达再试试';
|
||||
export const SEARCH_EXCEPTION_TIP = '查询出错啦,智能助理还不够聪明,请您换个表达再试试';
|
||||
|
||||
export const MSG_VALID_TIP = {
|
||||
[MsgValidTypeEnum.SEARCH_EXCEPTION]: '数据查询异常',
|
||||
[MsgValidTypeEnum.INVALID]: '小Q不太懂您说什么呐,回去一定补充知识',
|
||||
[MsgValidTypeEnum.INVALID]: '智能助理不太懂您说什么呐,回去一定补充知识',
|
||||
};
|
||||
|
||||
export const PREFIX_CLS = 'ss-chat';
|
||||
|
||||
@@ -51,10 +51,21 @@ export type FilterItemType = {
|
||||
value: string[];
|
||||
};
|
||||
|
||||
export type ModelType = {
|
||||
alias: string;
|
||||
bizName: string;
|
||||
id: number;
|
||||
model: number;
|
||||
name: string;
|
||||
type: string;
|
||||
useCnt: number;
|
||||
}
|
||||
|
||||
export type ChatContextType = {
|
||||
aggType: string;
|
||||
modelId: number;
|
||||
modelName: string;
|
||||
model: ModelType;
|
||||
dateInfo: DateInfoType;
|
||||
dimensions: FieldType[];
|
||||
metrics: FieldType[];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Spin } from 'antd';
|
||||
import { PREFIX_CLS } from '../../common/constants';
|
||||
import { MsgDataType } from '../../common/type';
|
||||
import ChatMsg from '../ChatMsg';
|
||||
@@ -8,6 +9,7 @@ import Typing from './Typing';
|
||||
type Props = {
|
||||
question: string;
|
||||
executeLoading: boolean;
|
||||
entitySwitchLoading: boolean;
|
||||
chartIndex: number;
|
||||
executeTip?: string;
|
||||
data?: MsgDataType;
|
||||
@@ -21,6 +23,7 @@ type Props = {
|
||||
const ExecuteItem: React.FC<Props> = ({
|
||||
question,
|
||||
executeLoading,
|
||||
entitySwitchLoading,
|
||||
chartIndex,
|
||||
executeTip,
|
||||
data,
|
||||
@@ -50,13 +53,15 @@ const ExecuteItem: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className={`${prefixCls}-msg-content`}>
|
||||
<ChatMsg
|
||||
question={question}
|
||||
data={data}
|
||||
chartIndex={chartIndex}
|
||||
isMobileMode={isMobileMode}
|
||||
triggerResize={triggerResize}
|
||||
/>
|
||||
<Spin spinning={entitySwitchLoading}>
|
||||
<ChatMsg
|
||||
question={question}
|
||||
data={data}
|
||||
chartIndex={chartIndex}
|
||||
isMobileMode={isMobileMode}
|
||||
triggerResize={triggerResize}
|
||||
/>
|
||||
</Spin>
|
||||
{!isMetricCard && (
|
||||
<Tools
|
||||
data={data}
|
||||
|
||||
@@ -76,17 +76,7 @@ const ParseTip: React.FC<Props> = ({
|
||||
const entityAlias = entity?.alias?.[0]?.split('.')?.[0];
|
||||
const entityName = elementMatches?.find(item => item.element?.type === 'ID')?.element.name;
|
||||
|
||||
const pluginName = properties?.CONTEXT?.plugin?.name;
|
||||
|
||||
const modeName = pluginName
|
||||
? '调插件'
|
||||
: queryMode.includes('METRIC')
|
||||
? '算指标'
|
||||
: queryMode === 'ENTITY_DETAIL'
|
||||
? '查明细'
|
||||
: queryMode === 'ENTITY_LIST_FILTER'
|
||||
? '做圈选'
|
||||
: '';
|
||||
const { type: agentType, name: agentName } = properties || {};
|
||||
|
||||
const fields =
|
||||
queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems;
|
||||
@@ -101,11 +91,10 @@ const ParseTip: React.FC<Props> = ({
|
||||
}}
|
||||
>
|
||||
{index !== undefined && <div>{index + 1}.</div>}
|
||||
{!pluginName && isOptions && <div className={`${prefixCls}-mode-name`}>{modeName}:</div>}
|
||||
{!!pluginName ? (
|
||||
{!!agentType ? (
|
||||
<div className={`${prefixCls}-tip-item`}>
|
||||
将由问答插件
|
||||
<span className={itemValueClass}>{pluginName}</span>来解答
|
||||
将由{agentType === 'plugin' ? '插件' : '内置'}工具
|
||||
<span className={itemValueClass}>{agentName}</span>来解答
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -123,7 +112,7 @@ const ParseTip: React.FC<Props> = ({
|
||||
<div className={itemValueClass}>{modelName}</div>
|
||||
</div>
|
||||
)}
|
||||
{modeName === '算指标' && metric && (
|
||||
{metric && (
|
||||
<div className={`${prefixCls}-tip-item`}>
|
||||
<div className={`${prefixCls}-tip-item-name`}>指标:</div>
|
||||
<div className={itemValueClass}>{metric.name}</div>
|
||||
@@ -153,9 +142,13 @@ const ParseTip: React.FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{['METRIC_FILTER', 'METRIC_ENTITY', 'ENTITY_DETAIL', 'ENTITY_LIST_FILTER'].includes(
|
||||
queryMode
|
||||
) &&
|
||||
{[
|
||||
'METRIC_FILTER',
|
||||
'METRIC_ENTITY',
|
||||
'ENTITY_DETAIL',
|
||||
'ENTITY_LIST_FILTER',
|
||||
'ENTITY_ID',
|
||||
].includes(queryMode) &&
|
||||
dimensionFilters &&
|
||||
dimensionFilters?.length > 0 && (
|
||||
<div className={`${prefixCls}-tip-item`}>
|
||||
@@ -198,10 +191,10 @@ const ParseTip: React.FC<Props> = ({
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const pluginName = parseInfoOptions[0]?.properties?.CONTEXT?.plugin?.name;
|
||||
const agentType = parseInfoOptions[0]?.properties?.type;
|
||||
tipNode = (
|
||||
<div className={`${prefixCls}-tip`}>
|
||||
<div>{!!pluginName ? '您的问题' : '您的问题解析为:'}</div>
|
||||
<div>{!!agentType ? '您的问题' : '您的问题解析为:'}</div>
|
||||
{getTipNode(parseInfoOptions[0])}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,11 +5,14 @@ import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/
|
||||
import IconFont from '../IconFont';
|
||||
import ParseTip from './ParseTip';
|
||||
import ExecuteItem from './ExecuteItem';
|
||||
import { isMobile } from '../../utils/utils';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
msg: string;
|
||||
conversationId?: number;
|
||||
modelId?: number;
|
||||
agentId?: number;
|
||||
filter?: any[];
|
||||
isLastMessage?: boolean;
|
||||
msgData?: MsgDataType;
|
||||
@@ -24,6 +27,7 @@ const ChatItem: React.FC<Props> = ({
|
||||
msg,
|
||||
conversationId,
|
||||
modelId,
|
||||
agentId,
|
||||
filter,
|
||||
isLastMessage,
|
||||
isMobileMode,
|
||||
@@ -41,7 +45,7 @@ const ChatItem: React.FC<Props> = ({
|
||||
const [executeLoading, setExecuteLoading] = useState(false);
|
||||
const [executeTip, setExecuteTip] = useState('');
|
||||
const [executeMode, setExecuteMode] = useState(false);
|
||||
const [entitySwitching, setEntitySwitching] = useState(false);
|
||||
const [entitySwitchLoading, setEntitySwitchLoading] = useState(false);
|
||||
|
||||
const [chartIndex, setChartIndex] = useState(0);
|
||||
|
||||
@@ -76,37 +80,42 @@ const ChatItem: React.FC<Props> = ({
|
||||
) => {
|
||||
setExecuteMode(true);
|
||||
setExecuteLoading(true);
|
||||
const { data } = await chatExecute(msg, conversationId!, parseInfoValue);
|
||||
setExecuteLoading(false);
|
||||
const valid = updateData(data);
|
||||
if (onMsgDataLoaded) {
|
||||
let parseOptions: ChatContextType[] = parseInfoOptions || [];
|
||||
if (
|
||||
parseInfoOptions &&
|
||||
parseInfoOptions.length > 1 &&
|
||||
(parseInfoOptions[0].queryMode.includes('METRIC') ||
|
||||
parseInfoOptions[0].queryMode.includes('ENTITY'))
|
||||
) {
|
||||
parseOptions = parseInfoOptions.filter(
|
||||
(item, index) =>
|
||||
index === 0 ||
|
||||
(!item.queryMode.includes('METRIC') && !item.queryMode.includes('ENTITY'))
|
||||
try {
|
||||
const { data } = await chatExecute(msg, conversationId!, parseInfoValue);
|
||||
setExecuteLoading(false);
|
||||
const valid = updateData(data);
|
||||
if (onMsgDataLoaded) {
|
||||
let parseOptions: ChatContextType[] = parseInfoOptions || [];
|
||||
if (
|
||||
parseInfoOptions &&
|
||||
parseInfoOptions.length > 1 &&
|
||||
(parseInfoOptions[0].queryMode.includes('METRIC') ||
|
||||
parseInfoOptions[0].queryMode.includes('ENTITY'))
|
||||
) {
|
||||
parseOptions = parseInfoOptions.filter(
|
||||
(item, index) =>
|
||||
index === 0 ||
|
||||
(!item.queryMode.includes('METRIC') && !item.queryMode.includes('ENTITY'))
|
||||
);
|
||||
}
|
||||
onMsgDataLoaded(
|
||||
{
|
||||
...data.data,
|
||||
chatContext: parseInfoValue,
|
||||
parseOptions: parseOptions.length > 1 ? parseOptions.slice(1) : undefined,
|
||||
},
|
||||
valid
|
||||
);
|
||||
}
|
||||
onMsgDataLoaded(
|
||||
{
|
||||
...data.data,
|
||||
chatContext: parseInfoValue,
|
||||
parseOptions: parseOptions.length > 1 ? parseOptions.slice(1) : undefined,
|
||||
},
|
||||
valid
|
||||
);
|
||||
} catch (e) {
|
||||
setExecuteLoading(false);
|
||||
setExecuteTip(SEARCH_EXCEPTION_TIP);
|
||||
}
|
||||
};
|
||||
|
||||
const onSendMsg = async () => {
|
||||
setParseLoading(true);
|
||||
const { data: parseData } = await chatParse(msg, conversationId, modelId, filter);
|
||||
const { data: parseData } = await chatParse(msg, conversationId, modelId, agentId, filter);
|
||||
setParseLoading(false);
|
||||
const { code, data } = parseData || {};
|
||||
const { state, selectedParses } = data || {};
|
||||
@@ -115,10 +124,9 @@ const ChatItem: React.FC<Props> = ({
|
||||
state === ParseStateEnum.FAILED ||
|
||||
selectedParses == null ||
|
||||
selectedParses.length === 0 ||
|
||||
(selectedParses.length === 1 &&
|
||||
!selectedParses[0]?.modelName &&
|
||||
!selectedParses[0]?.properties?.CONTEXT?.plugin?.name &&
|
||||
selectedParses[0]?.queryMode !== 'WEB_PAGE')
|
||||
(selectedParses.length > 0 &&
|
||||
!selectedParses[0]?.properties?.type &&
|
||||
!selectedParses[0]?.queryMode)
|
||||
) {
|
||||
setParseTip(PARSE_ERROR_TIP);
|
||||
return;
|
||||
@@ -146,9 +154,9 @@ const ChatItem: React.FC<Props> = ({
|
||||
}, [msg, msgData]);
|
||||
|
||||
const onSwitchEntity = async (entityId: string) => {
|
||||
setEntitySwitching(true);
|
||||
setEntitySwitchLoading(true);
|
||||
const res = await switchEntity(entityId, data?.chatContext?.modelId, conversationId || 0);
|
||||
setEntitySwitching(false);
|
||||
setEntitySwitchLoading(false);
|
||||
setData(res.data.data);
|
||||
};
|
||||
|
||||
@@ -164,11 +172,15 @@ const ChatItem: React.FC<Props> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const contentClass = classNames(`${prefixCls}-content`, {
|
||||
[`${prefixCls}-content-mobile`]: isMobile,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
<div className={`${prefixCls}-section`}>
|
||||
<IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />
|
||||
<div className={`${prefixCls}-content`}>
|
||||
{!isMobile && <IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />}
|
||||
<div className={contentClass}>
|
||||
<ParseTip
|
||||
parseLoading={parseLoading}
|
||||
parseInfoOptions={parseOptions || parseInfoOptions.slice(0, 1)}
|
||||
@@ -181,11 +193,12 @@ const ChatItem: React.FC<Props> = ({
|
||||
</div>
|
||||
{executeMode && data?.queryMode !== 'WEB_PAGE' && (
|
||||
<div className={`${prefixCls}-section`}>
|
||||
<IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />
|
||||
<div className={`${prefixCls}-content`}>
|
||||
{!isMobile && <IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />}
|
||||
<div className={contentClass}>
|
||||
<ExecuteItem
|
||||
question={msg}
|
||||
executeLoading={executeLoading}
|
||||
entitySwitchLoading={entitySwitchLoading}
|
||||
executeTip={executeTip}
|
||||
chartIndex={chartIndex}
|
||||
data={data}
|
||||
|
||||
@@ -117,6 +117,10 @@
|
||||
width: calc(100% - 50px);
|
||||
}
|
||||
|
||||
&-content-mobile {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-metric-info-list {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
@@ -136,7 +140,6 @@
|
||||
|
||||
&-typing-bubble {
|
||||
width: fit-content;
|
||||
// padding: 16px !important;
|
||||
}
|
||||
|
||||
&-text-bubble {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { PREFIX_CLS } from '../../../common/constants';
|
||||
type Props = {
|
||||
position: 'left' | 'right';
|
||||
width?: number | string;
|
||||
maxWidth?: number | string;
|
||||
height?: number | string;
|
||||
title?: string;
|
||||
followQuestions?: string[];
|
||||
@@ -17,6 +18,7 @@ type Props = {
|
||||
|
||||
const Message: React.FC<Props> = ({
|
||||
width,
|
||||
maxWidth,
|
||||
height,
|
||||
children,
|
||||
bubbleClassName,
|
||||
@@ -38,7 +40,7 @@ const Message: React.FC<Props> = ({
|
||||
<div className={`${prefixCls}-body`}>
|
||||
<div
|
||||
className={`${prefixCls}-bubble${bubbleClassName ? ` ${bubbleClassName}` : ''}`}
|
||||
style={{ width, height }}
|
||||
style={{ width, height, maxWidth }}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
|
||||
@@ -43,7 +43,11 @@ const MetricCard: React.FC<Props> = ({
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
<div className={`${prefixCls}-top-bar`}>
|
||||
<div className={`${prefixCls}-indicator-name`}>{indicatorColumn?.name}</div>
|
||||
{indicatorColumn?.name ? (
|
||||
<div className={`${prefixCls}-indicator-name`}>{indicatorColumn?.name}</div>
|
||||
) : (
|
||||
<div style={{ height: 32 }} />
|
||||
)}
|
||||
{(hasFilterSection || drillDownDimension) && (
|
||||
<div className={`${prefixCls}-filter-section-wrapper`}>
|
||||
(
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
&-drill-down-dimensions {
|
||||
position: absolute;
|
||||
bottom: -38px;
|
||||
left: 0;
|
||||
bottom: -44px;
|
||||
left: -16;
|
||||
}
|
||||
}
|
||||
@@ -19,25 +19,19 @@ type Props = {
|
||||
};
|
||||
|
||||
const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApplyAuth }) => {
|
||||
const { queryColumns, queryResults, entityInfo, chatContext, queryMode, aggregateInfo } = data;
|
||||
|
||||
const { dateMode, unit } = chatContext?.dateInfo || {};
|
||||
|
||||
const { entityInfo, chatContext, queryMode } = data;
|
||||
const { dateInfo, dimensionFilters, elementMatches } = chatContext || {};
|
||||
const { dateMode, unit } = dateInfo || {};
|
||||
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY;
|
||||
const initialDateOption = dateOptions.find((option: any) => {
|
||||
return dateMode === 'RECENT' && option.value === unit;
|
||||
})?.value;
|
||||
|
||||
const [columns, setColumns] = useState<ColumnType[]>(queryColumns || []);
|
||||
const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER');
|
||||
|
||||
const [activeMetricField, setActiveMetricField] = useState<FieldType>(chatContext.metrics?.[0]);
|
||||
const [dataSource, setDataSource] = useState<any[]>(queryResults);
|
||||
const [currentDateOption, setCurrentDateOption] = useState<number>(initialDateOption);
|
||||
const [dimensions, setDimensions] = useState<FieldType[]>(chatContext?.dimensions);
|
||||
const [columns, setColumns] = useState<ColumnType[]>([]);
|
||||
const [activeMetricField, setActiveMetricField] = useState<FieldType>();
|
||||
const [dataSource, setDataSource] = useState<any[]>([]);
|
||||
const [currentDateOption, setCurrentDateOption] = useState<number>();
|
||||
const [dimensions, setDimensions] = useState<FieldType[]>();
|
||||
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
|
||||
const [aggregateInfoValue, setAggregateInfoValue] = useState<any>(aggregateInfo);
|
||||
const [dateModeValue, setDateModeValue] = useState(dateMode);
|
||||
const [aggregateInfoValue, setAggregateInfoValue] = useState<any>();
|
||||
const [dateModeValue, setDateModeValue] = useState<any>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const dateField: any = columns.find(
|
||||
@@ -47,9 +41,31 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
const categoryColumnName =
|
||||
columns.find((column: any) => column.showType === 'CATEGORY')?.nameEn || '';
|
||||
|
||||
const entityId = dimensionFilters?.length > 0 ? dimensionFilters[0].value : undefined;
|
||||
const entityName = elementMatches?.find((item: any) => item.element?.type === 'ID')?.element
|
||||
?.name;
|
||||
|
||||
const isEntityMode =
|
||||
(queryMode === 'ENTITY_LIST_FILTER' || queryMode === 'METRIC_ENTITY') &&
|
||||
typeof entityId === 'string' &&
|
||||
entityName !== undefined;
|
||||
|
||||
useEffect(() => {
|
||||
const { queryColumns, queryResults, chatContext, aggregateInfo } = data;
|
||||
|
||||
const initialDateOption = dateOptions.find((option: any) => {
|
||||
return dateMode === 'RECENT' && option.value === unit;
|
||||
})?.value;
|
||||
|
||||
setColumns(queryColumns || []);
|
||||
setActiveMetricField(chatContext?.metrics?.[0]);
|
||||
setDataSource(queryResults);
|
||||
}, [queryResults]);
|
||||
setCurrentDateOption(initialDateOption);
|
||||
setDimensions(chatContext?.dimensions);
|
||||
setDrillDownDimension(undefined);
|
||||
setAggregateInfoValue(aggregateInfo);
|
||||
setDateModeValue(chatContext?.dateInfo?.dateMode);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (queryMode === 'METRIC_GROUPBY') {
|
||||
@@ -117,14 +133,14 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
});
|
||||
};
|
||||
|
||||
const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER');
|
||||
|
||||
if (!currentMetricField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const prefixCls = `${CLS_PREFIX}-metric-trend`;
|
||||
|
||||
const { dimensionFilters } = chatContext || {};
|
||||
|
||||
const hasFilterSection = dimensionFilters?.length > 0;
|
||||
|
||||
return (
|
||||
@@ -174,59 +190,61 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{aggregateInfoValue?.metricInfos?.length > 0 && (
|
||||
<MetricInfo aggregateInfo={aggregateInfoValue} />
|
||||
)}
|
||||
<div className={`${prefixCls}-date-options`}>
|
||||
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => {
|
||||
const dateOptionClass = classNames(`${prefixCls}-date-option`, {
|
||||
[`${prefixCls}-date-active`]: dateOption.value === currentDateOption,
|
||||
[`${prefixCls}-date-mobile`]: isMobile,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={dateOption.value}
|
||||
className={dateOptionClass}
|
||||
onClick={() => {
|
||||
selectDateOption(dateOption.value);
|
||||
}}
|
||||
>
|
||||
{dateOption.label}
|
||||
{dateOption.value === currentDateOption && (
|
||||
<div className={`${prefixCls}-active-identifier`} />
|
||||
)}
|
||||
</div>
|
||||
{index !== dateOptions.length - 1 && (
|
||||
<div className={`${prefixCls}-date-option-divider`} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Spin spinning={loading}>
|
||||
{dataSource?.length === 1 || chartIndex % 2 === 1 ? (
|
||||
<Table data={{ ...data, queryResults: dataSource }} onApplyAuth={onApplyAuth} />
|
||||
) : (
|
||||
<MetricTrendChart
|
||||
model={entityInfo?.modelInfo.name}
|
||||
dateColumnName={dateColumnName}
|
||||
categoryColumnName={categoryColumnName}
|
||||
metricField={currentMetricField}
|
||||
resultList={dataSource}
|
||||
triggerResize={triggerResize}
|
||||
onApplyAuth={onApplyAuth}
|
||||
<div className={`${prefixCls}-content`}>
|
||||
{aggregateInfoValue?.metricInfos?.length > 0 && (
|
||||
<MetricInfo aggregateInfo={aggregateInfoValue} />
|
||||
)}
|
||||
<div className={`${prefixCls}-date-options`}>
|
||||
{dateOptions.map((dateOption: { label: string; value: number }, index: number) => {
|
||||
const dateOptionClass = classNames(`${prefixCls}-date-option`, {
|
||||
[`${prefixCls}-date-active`]: dateOption.value === currentDateOption,
|
||||
[`${prefixCls}-date-mobile`]: isMobile,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
key={dateOption.value}
|
||||
className={dateOptionClass}
|
||||
onClick={() => {
|
||||
selectDateOption(dateOption.value);
|
||||
}}
|
||||
>
|
||||
{dateOption.label}
|
||||
{dateOption.value === currentDateOption && (
|
||||
<div className={`${prefixCls}-active-identifier`} />
|
||||
)}
|
||||
</div>
|
||||
{index !== dateOptions.length - 1 && (
|
||||
<div className={`${prefixCls}-date-option-divider`} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{dataSource?.length === 1 || chartIndex % 2 === 1 ? (
|
||||
<Table data={{ ...data, queryResults: dataSource }} onApplyAuth={onApplyAuth} />
|
||||
) : (
|
||||
<MetricTrendChart
|
||||
model={entityInfo?.modelInfo.name}
|
||||
dateColumnName={dateColumnName}
|
||||
categoryColumnName={categoryColumnName}
|
||||
metricField={currentMetricField}
|
||||
resultList={dataSource}
|
||||
triggerResize={triggerResize}
|
||||
onApplyAuth={onApplyAuth}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{queryMode.includes('METRIC') && !isEntityMode && (
|
||||
<DrillDownDimensions
|
||||
modelId={chatContext.modelId}
|
||||
drillDownDimension={drillDownDimension}
|
||||
dimensionFilters={chatContext.dimensionFilters}
|
||||
onSelectDimension={onSelectDimension}
|
||||
/>
|
||||
)}
|
||||
</Spin>
|
||||
{queryMode.includes('METRIC') && (
|
||||
<DrillDownDimensions
|
||||
modelId={chatContext.modelId}
|
||||
drillDownDimension={drillDownDimension}
|
||||
dimensionFilters={chatContext.dimensionFilters}
|
||||
onSelectDimension={onSelectDimension}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -48,6 +48,13 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
row-gap: 12px;
|
||||
}
|
||||
|
||||
&-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -5,7 +5,7 @@ import MetricCard from './MetricCard';
|
||||
import MetricTrend from './MetricTrend';
|
||||
import Table from './Table';
|
||||
import { ColumnType, DrillDownDimensionType, MsgDataType } from '../../common/type';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { queryData } from '../../service';
|
||||
|
||||
type Props = {
|
||||
@@ -25,6 +25,11 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setColumns(queryColumns);
|
||||
setDataSource(queryResults);
|
||||
}, [queryColumns, queryResults]);
|
||||
|
||||
if (!queryColumns || !queryResults) {
|
||||
return null;
|
||||
}
|
||||
@@ -35,14 +40,15 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
const metricFields = columns.filter(item => item.showType === 'NUMBER');
|
||||
|
||||
const isMetricCard =
|
||||
queryMode.includes('METRIC') &&
|
||||
(queryMode.includes('METRIC') ||
|
||||
(queryMode === 'DSL' && singleData && metricFields.length === 1 && columns.length === 1)) &&
|
||||
(singleData || chatContext?.dateInfo?.startDate === chatContext?.dateInfo?.endDate);
|
||||
|
||||
const isText =
|
||||
columns.length === 1 &&
|
||||
columns[0].showType === 'CATEGORY' &&
|
||||
!queryMode.includes('METRIC') &&
|
||||
!queryMode.includes('ENTITY') &&
|
||||
((!queryMode.includes('METRIC') && !queryMode.includes('ENTITY')) ||
|
||||
queryMode === 'METRIC_INTERPRET') &&
|
||||
singleData;
|
||||
|
||||
const onLoadData = async (value: any) => {
|
||||
@@ -68,9 +74,43 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
|
||||
const getMsgContent = () => {
|
||||
if (isText) {
|
||||
let text = dataSource[0][columns[0].nameEn];
|
||||
let htmlCode: string;
|
||||
const match = text.match(/```html([\s\S]*?)```/);
|
||||
htmlCode = match && match[1].trim();
|
||||
if (htmlCode) {
|
||||
text = text.replace(/```html([\s\S]*?)```/, '');
|
||||
}
|
||||
let scriptCode: string;
|
||||
let scriptSrc: string;
|
||||
if (htmlCode) {
|
||||
scriptSrc = htmlCode.match(/<script src="([\s\S]*?)"><\/script>/)?.[1] || '';
|
||||
scriptCode =
|
||||
htmlCode.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);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div style={{ lineHeight: '24px', width: 'fit-content' }}>
|
||||
{dataSource[0][columns[0].nameEn]}
|
||||
<div
|
||||
style={{
|
||||
lineHeight: '24px',
|
||||
width: 'fit-content',
|
||||
maxWidth: '100%',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
{htmlCode ? <pre>{text}</pre> : text}
|
||||
{!!htmlCode && <div dangerouslySetInnerHTML={{ __html: htmlCode }} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -103,15 +143,18 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Bar
|
||||
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
|
||||
triggerResize={triggerResize}
|
||||
loading={loading}
|
||||
drillDownDimension={drillDownDimension}
|
||||
onSelectDimension={onSelectDimension}
|
||||
/>
|
||||
);
|
||||
if (categoryField?.length > 0 && metricFields?.length > 0) {
|
||||
return (
|
||||
<Bar
|
||||
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
|
||||
triggerResize={triggerResize}
|
||||
loading={loading}
|
||||
drillDownDimension={drillDownDimension}
|
||||
onSelectDimension={onSelectDimension}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />;
|
||||
};
|
||||
|
||||
let width = '100%';
|
||||
@@ -135,6 +178,7 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
title={question}
|
||||
isMobileMode={isMobileMode}
|
||||
width={width}
|
||||
maxWidth={isText && !isMobile ? '80%' : undefined}
|
||||
queryMode={queryMode}
|
||||
>
|
||||
{getMsgContent()}
|
||||
|
||||
@@ -29,6 +29,15 @@ const Tools: React.FC<Props> = ({
|
||||
const { queryColumns, queryResults, queryId, chatContext, queryMode, entityInfo } = data || {};
|
||||
const [score, setScore] = useState(scoreValue || 0);
|
||||
|
||||
const { dimensionFilters, elementMatches } = data.chatContext;
|
||||
|
||||
const entityId = dimensionFilters?.length > 0 ? dimensionFilters[0].value : undefined;
|
||||
const entityName = elementMatches?.find((item: any) => item.element?.type === 'ID')?.element
|
||||
?.name;
|
||||
|
||||
const isEntityMode =
|
||||
queryMode === 'ENTITY_LIST_FILTER' && typeof entityId === 'string' && entityName !== undefined;
|
||||
|
||||
const prefixCls = `${CLS_PREFIX}-tools`;
|
||||
|
||||
const singleData = queryResults.length === 1;
|
||||
@@ -41,7 +50,8 @@ const Tools: React.FC<Props> = ({
|
||||
queryColumns[0].showType === 'CATEGORY' &&
|
||||
queryResults?.length === 1) ||
|
||||
(!queryMode.includes('METRIC') && !queryMode.includes('ENTITY')) ||
|
||||
isMetricCard;
|
||||
isMetricCard ||
|
||||
isEntityMode;
|
||||
|
||||
const changeChart = () => {
|
||||
onChangeChart();
|
||||
@@ -75,7 +85,7 @@ const Tools: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
{/* {isLastMessage && chatContext?.modelId && entityInfo?.entityId && (
|
||||
{isLastMessage && chatContext?.modelId && entityInfo?.entityId && (
|
||||
<Popover
|
||||
content={
|
||||
<RecommendOptions
|
||||
@@ -93,7 +103,7 @@ const Tools: React.FC<Props> = ({
|
||||
>
|
||||
<Button shape="round">切换其他匹配内容</Button>
|
||||
</Popover>
|
||||
)} */}
|
||||
)}
|
||||
{!isMobile && (
|
||||
<>
|
||||
{queryMode === 'METRIC_FILTER' && (
|
||||
|
||||
@@ -55,6 +55,7 @@ const Chat = () => {
|
||||
<ChatItem
|
||||
msg={msg}
|
||||
// msgData={data}
|
||||
agentId={6}
|
||||
onMsgDataLoaded={onMsgDataLoaded}
|
||||
isLastMessage
|
||||
isMobileMode
|
||||
|
||||
@@ -7,7 +7,7 @@ const axiosInstance: AxiosInstance = axios.create({
|
||||
// 设置基本URL,所有请求都会使用这个URL作为前缀
|
||||
baseURL: '',
|
||||
// 设置请求超时时间(毫秒)
|
||||
timeout: 60000,
|
||||
timeout: 120000,
|
||||
// 设置请求头
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -25,11 +25,12 @@ export function chatQuery(queryText: string, chatId?: number, modelId?: number,
|
||||
});
|
||||
}
|
||||
|
||||
export function chatParse(queryText: string, chatId?: number, modelId?: number, filters?: any[]) {
|
||||
export function chatParse(queryText: string, chatId?: number, modelId?: number, agentId?: number, filters?: any[]) {
|
||||
return axios.post<Result<ParseDataType>>(`${prefix}/chat/query/parse`, {
|
||||
queryText,
|
||||
chatId: chatId || DEFAULT_CHAT_ID,
|
||||
modelId,
|
||||
agentId,
|
||||
queryFilters: filters ? {
|
||||
filters
|
||||
} : undefined,
|
||||
@@ -63,10 +64,6 @@ export function queryContext(queryText: string, chatId?: number) {
|
||||
});
|
||||
}
|
||||
|
||||
export function querySuggestionInfo(modelId: number) {
|
||||
return axios.get<Result<any>>(`${prefix}/chat/recommend/${modelId}`);
|
||||
}
|
||||
|
||||
export function getHistoryMsg(current: number, chatId: number = DEFAULT_CHAT_ID, pageSize: number = 10) {
|
||||
return axios.post<Result<HistoryType>>(`${prefix}/chat/manage/pageQueryInfo?chatId=${chatId}`, {
|
||||
current,
|
||||
@@ -74,22 +71,6 @@ export function getHistoryMsg(current: number, chatId: number = DEFAULT_CHAT_ID,
|
||||
});
|
||||
}
|
||||
|
||||
export function queryMetricInfo(data: any) {
|
||||
return axios.get(`/semantic/metric/getMetric/${data.classId}/${data.uniqueId}`);
|
||||
}
|
||||
|
||||
export function getRelatedDimensionFromStatInfo(data: any) {
|
||||
return axios.get(
|
||||
`/semantic/metric/getRelatedDimensionFromStatInfo/${data.classId}/${data.uniqueId}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function getMetricQueryInfo(data: any) {
|
||||
return axios.get<any>(
|
||||
`/openapi/bd-bi/api/polaris/intelligentQuery/getMetricQueryInfo/${data.classId}/${data.metricName}`
|
||||
);
|
||||
}
|
||||
|
||||
export function saveConversation(chatName: string) {
|
||||
return axios.post<Result<any>>(`${prefix}/chat/manage/save?chatName=${chatName}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user