mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-11 03:58:14 +00:00
(feature)(webapp) add filter modify and similar questions (#213)
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@uiw/react-watermark": "^0.0.5",
|
||||
"ahooks": "^3.7.8",
|
||||
"antd": "^5.5.2",
|
||||
"antd": "^4.23.5",
|
||||
"axios": "^0.21.1",
|
||||
"classnames": "^2.3.2",
|
||||
"echarts": "^5.4.2",
|
||||
|
||||
@@ -75,7 +75,6 @@ const MessageContainer: React.FC<Props> = ({
|
||||
identityMsg,
|
||||
msgData,
|
||||
score,
|
||||
parseOptions,
|
||||
filters,
|
||||
} = msgItem;
|
||||
|
||||
@@ -108,24 +107,6 @@ const MessageContainer: React.FC<Props> = ({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{type === MessageTypeEnum.PARSE_OPTIONS && (
|
||||
<ChatItem
|
||||
msg={msgValue || msg || ''}
|
||||
conversationId={chatId}
|
||||
modelId={modelId}
|
||||
agentId={agentId}
|
||||
filter={filters}
|
||||
isLastMessage={index === messageList.length - 1}
|
||||
triggerResize={triggerResize}
|
||||
parseOptions={parseOptions}
|
||||
integrateSystem={integrateSystem}
|
||||
onMsgDataLoaded={(data: MsgDataType, valid: boolean) => {
|
||||
onMsgDataLoaded(data, msgId, msgValue || msg || '', valid);
|
||||
}}
|
||||
onUpdateMessageScroll={updateMessageContainerScroll}
|
||||
onSendMsg={onSendMsg}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -21,7 +21,7 @@ export enum SemanticTypeEnum {
|
||||
}
|
||||
|
||||
export const SEMANTIC_TYPE_MAP = {
|
||||
[SemanticTypeEnum.MODEL]: '数据来源',
|
||||
[SemanticTypeEnum.MODEL]: '数据模型',
|
||||
[SemanticTypeEnum.DIMENSION]: '维度',
|
||||
[SemanticTypeEnum.METRIC]: '指标',
|
||||
[SemanticTypeEnum.VALUE]: '维度值',
|
||||
|
||||
@@ -309,21 +309,11 @@ const Chat: ForwardRefRenderFunction<any, Props> = (
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
let parseOptionsItem: any;
|
||||
if (data.parseOptions && data.parseOptions.length > 0) {
|
||||
parseOptionsItem = {
|
||||
id: uuid(),
|
||||
msg: messageList[messageList.length - 1]?.msg,
|
||||
type: MessageTypeEnum.PARSE_OPTIONS,
|
||||
parseOptions: data.parseOptions,
|
||||
};
|
||||
}
|
||||
const msgs = cloneDeep(messageList);
|
||||
const msg = msgs.find(item => item.id === questionId);
|
||||
if (msg) {
|
||||
msg.msgData = data;
|
||||
const msgList = [...msgs, ...(parseOptionsItem ? [parseOptionsItem] : [])];
|
||||
setMessageList(msgList);
|
||||
setMessageList(msgs);
|
||||
}
|
||||
updateMessageContainerScroll(`${questionId}`);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChatContextType, MsgDataType, SendMsgParamsType } from "../common/type";
|
||||
import { MsgDataType, SendMsgParamsType } from "../common/type";
|
||||
|
||||
export enum MessageTypeEnum {
|
||||
TEXT = 'text', // 指标文本
|
||||
@@ -10,7 +10,6 @@ export enum MessageTypeEnum {
|
||||
PLUGIN = 'PLUGIN', // 插件
|
||||
WEB_PAGE = 'WEB_PAGE', // 插件
|
||||
RECOMMEND_QUESTIONS = 'recommend_questions', // 推荐问题
|
||||
PARSE_OPTIONS = 'parse_options', // 解析选项
|
||||
AGENT_LIST = 'agent_list', // 专家列表
|
||||
}
|
||||
|
||||
@@ -27,7 +26,6 @@ export type MessageItem = {
|
||||
quote?: string;
|
||||
score?: number;
|
||||
feedback?: string;
|
||||
parseOptions?: ChatContextType[];
|
||||
filters?: any;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export type SearchRecommendItem = {
|
||||
|
||||
export type FieldType = {
|
||||
bizName: string;
|
||||
itemId: number;
|
||||
id: number;
|
||||
name: string;
|
||||
status: number;
|
||||
@@ -86,7 +87,7 @@ export type ChatContextType = {
|
||||
dimensions: FieldType[];
|
||||
metrics: FieldType[];
|
||||
entity: { alias: string[], id: number };
|
||||
entityInfo: { dimensions: EntityDimensionType[] };
|
||||
entityInfo: EntityInfoType;
|
||||
elementMatches: any[];
|
||||
nativeQuery: boolean;
|
||||
queryMode: string;
|
||||
@@ -133,6 +134,7 @@ export type MsgDataType = {
|
||||
queryId: number;
|
||||
queryMode: string;
|
||||
queryState: string;
|
||||
queryText: string;
|
||||
response: PluginResonseType;
|
||||
parseOptions?: ChatContextType[];
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Spin } from 'antd';
|
||||
import { CheckCircleFilled } from '@ant-design/icons';
|
||||
import { Button, Spin } from 'antd';
|
||||
import { CheckCircleFilled, ReloadOutlined } from '@ant-design/icons';
|
||||
import { PREFIX_CLS } from '../../common/constants';
|
||||
import { MsgDataType } from '../../common/type';
|
||||
import ChatMsg from '../ChatMsg';
|
||||
import WebPage from '../ChatMsg/WebPage';
|
||||
import Loading from './Loading';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
type Props = {
|
||||
queryId?: number;
|
||||
@@ -12,8 +13,11 @@ type Props = {
|
||||
entitySwitchLoading: boolean;
|
||||
chartIndex: number;
|
||||
executeTip?: string;
|
||||
executeItemNode?: ReactNode;
|
||||
renderCustomExecuteNode?: boolean;
|
||||
data?: MsgDataType;
|
||||
triggerResize?: boolean;
|
||||
onRefresh: () => void;
|
||||
};
|
||||
|
||||
const ExecuteItem: React.FC<Props> = ({
|
||||
@@ -22,12 +26,15 @@ const ExecuteItem: React.FC<Props> = ({
|
||||
entitySwitchLoading,
|
||||
chartIndex,
|
||||
executeTip,
|
||||
executeItemNode,
|
||||
renderCustomExecuteNode,
|
||||
data,
|
||||
triggerResize,
|
||||
onRefresh,
|
||||
}) => {
|
||||
const prefixCls = `${PREFIX_CLS}-item`;
|
||||
|
||||
const getNodeTip = (title: string, tip?: string) => {
|
||||
const getNodeTip = (title: ReactNode, tip?: string) => {
|
||||
return (
|
||||
<>
|
||||
<div className={`${prefixCls}-title-bar`}>
|
||||
@@ -42,12 +49,19 @@ const ExecuteItem: React.FC<Props> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const reloadNode = (
|
||||
<Button className={`${prefixCls}-reload`} size="small" onClick={onRefresh}>
|
||||
<ReloadOutlined />
|
||||
重新查询
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (executeLoading) {
|
||||
return getNodeTip('数据查询中');
|
||||
}
|
||||
|
||||
if (executeTip) {
|
||||
return getNodeTip('数据查询失败', executeTip);
|
||||
return getNodeTip(<>数据查询失败:{reloadNode}</>, executeTip);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
@@ -58,14 +72,19 @@ 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`}>
|
||||
数据查询:
|
||||
{reloadNode}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${prefixCls}-content-container ${prefixCls}-last-node`}>
|
||||
<div className={`${prefixCls}-content-container`}>
|
||||
<Spin spinning={entitySwitchLoading}>
|
||||
{data.queryAuthorization?.message && (
|
||||
<div className={`${prefixCls}-auth-tip`}>提示:{data.queryAuthorization.message}</div>
|
||||
)}
|
||||
{data?.queryMode === 'WEB_PAGE' ? (
|
||||
{renderCustomExecuteNode && executeItemNode ? (
|
||||
executeItemNode
|
||||
) : data?.queryMode === 'WEB_PAGE' ? (
|
||||
<WebPage id={queryId!} data={data} />
|
||||
) : (
|
||||
<ChatMsg
|
||||
|
||||
@@ -1,27 +1,46 @@
|
||||
import { Select, Spin } from 'antd';
|
||||
import { Select, Spin, InputNumber } from 'antd';
|
||||
import { PREFIX_CLS } from '../../common/constants';
|
||||
import { FilterItemType } from '../../common/type';
|
||||
import { ChatContextType, FilterItemType } from '../../common/type';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { queryDimensionValues } from '../../service';
|
||||
import debounce from 'lodash/debounce';
|
||||
import isArray from 'lodash/isArray';
|
||||
import { debounce, isArray } from 'lodash';
|
||||
import SwicthEntity from './SwitchEntity';
|
||||
|
||||
type Props = {
|
||||
modelId: number;
|
||||
filters: FilterItemType[];
|
||||
filter: FilterItemType;
|
||||
chatContext: ChatContextType;
|
||||
agentId?: number;
|
||||
entityAlias?: string;
|
||||
onFiltersChange: (filters: FilterItemType[]) => void;
|
||||
onSwitchEntity: (entityId: string) => void;
|
||||
};
|
||||
|
||||
const FilterItem: React.FC<Props> = ({ modelId, filters, filter, onFiltersChange }) => {
|
||||
const [options, setOptions] = useState<{ label: string; value: string }[]>([]);
|
||||
const FilterItem: React.FC<Props> = ({
|
||||
modelId,
|
||||
filters,
|
||||
filter,
|
||||
chatContext,
|
||||
agentId,
|
||||
entityAlias,
|
||||
onFiltersChange,
|
||||
onSwitchEntity,
|
||||
}) => {
|
||||
const [options, setOptions] = useState<{ label: string; value: string | null }[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const fetchRef = useRef(0);
|
||||
|
||||
const prefixCls = `${PREFIX_CLS}-filter-item`;
|
||||
|
||||
const initData = async () => {
|
||||
const { data } = await queryDimensionValues(modelId, filter.bizName, '');
|
||||
const { data } = await queryDimensionValues(
|
||||
modelId,
|
||||
filter.bizName,
|
||||
agentId!,
|
||||
filter.elementID,
|
||||
''
|
||||
);
|
||||
setOptions(
|
||||
data?.resultList.map((item: any) => ({
|
||||
label: item[filter.bizName],
|
||||
@@ -42,31 +61,56 @@ const FilterItem: React.FC<Props> = ({ modelId, filters, filter, onFiltersChange
|
||||
const fetchId = fetchRef.current;
|
||||
setOptions([]);
|
||||
setLoading(true);
|
||||
|
||||
queryDimensionValues(modelId, filter.bizName, value).then(newOptions => {
|
||||
if (fetchId !== fetchRef.current) {
|
||||
return;
|
||||
queryDimensionValues(modelId, filter.bizName, agentId!, filter.elementID, value).then(
|
||||
newOptions => {
|
||||
if (fetchId !== fetchRef.current) {
|
||||
return;
|
||||
}
|
||||
setOptions(
|
||||
newOptions.data?.resultList.map((item: any) => ({
|
||||
label: item[filter.bizName],
|
||||
value: item[filter.bizName],
|
||||
})) || []
|
||||
);
|
||||
setLoading(false);
|
||||
}
|
||||
setOptions(
|
||||
newOptions.data?.resultList.map((item: any) => ({
|
||||
label: item[filter.bizName],
|
||||
value: item[filter.bizName],
|
||||
})) || []
|
||||
);
|
||||
setLoading(false);
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
return debounce(loadOptions, 800);
|
||||
return debounce(loadOptions, 500);
|
||||
}, [queryDimensionValues]);
|
||||
|
||||
const onChange = (value: string | string[]) => {
|
||||
if (isArray(value) && value.length === 0) {
|
||||
return;
|
||||
}
|
||||
const onOperatorChange = (value: string) => {
|
||||
const newFilters = filters.map(item => {
|
||||
if (item.bizName === filter.bizName) {
|
||||
item.value = isArray(value) ? value : `${value}`;
|
||||
item.operator = value;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
onFiltersChange(newFilters);
|
||||
};
|
||||
|
||||
const onChange = (value: string | string[] | number | null) => {
|
||||
const newFilters = filters.map(item => {
|
||||
if (item.bizName === filter.bizName) {
|
||||
item.value =
|
||||
typeof filter.value === 'number' || filter.value === null
|
||||
? value
|
||||
: isArray(value)
|
||||
? value
|
||||
: `${value}`;
|
||||
if (isArray(value)) {
|
||||
if (value.length === 1) {
|
||||
item.operator = '=';
|
||||
item.value = value[0];
|
||||
} else {
|
||||
item.operator = 'IN';
|
||||
item.value = value;
|
||||
}
|
||||
} else {
|
||||
item.value =
|
||||
typeof filter.value === 'number' || filter.value === null ? value : `${value}`;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
});
|
||||
@@ -75,18 +119,46 @@ const FilterItem: React.FC<Props> = ({ modelId, filters, filter, onFiltersChange
|
||||
|
||||
return (
|
||||
<span className={prefixCls}>
|
||||
{(typeof filter.value === 'string' || isArray(filter.value)) &&
|
||||
(filter.operator === '=' || filter.operator === 'IN') ? (
|
||||
<span className={`${prefixCls}-filter-name`}>{filter.name}:</span>
|
||||
{(typeof filter.value === 'number' || filter.value === null) &&
|
||||
!filter.bizName?.includes('_id') ? (
|
||||
<>
|
||||
<Select
|
||||
options={[
|
||||
{ label: '大于', value: '>' },
|
||||
{ label: '等于', value: '=' },
|
||||
{ label: '小于', value: '<' },
|
||||
]}
|
||||
className={`${prefixCls}-operator-control`}
|
||||
value={filter.operator}
|
||||
onChange={onOperatorChange}
|
||||
/>
|
||||
<InputNumber
|
||||
className={`${prefixCls}-input-number-control`}
|
||||
value={filter.value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</>
|
||||
) : (typeof filter.value === 'string' || isArray(filter.value)) &&
|
||||
!filter.bizName?.includes('_id') ? (
|
||||
<Select
|
||||
bordered={false}
|
||||
value={filter.value}
|
||||
options={options}
|
||||
options={options.filter(option => option.value !== '' && option.value !== null)}
|
||||
className={`${prefixCls}-select-control`}
|
||||
onSearch={debounceFetcher}
|
||||
notFoundContent={loading ? <Spin size="small" /> : null}
|
||||
onChange={onChange}
|
||||
mode={isArray(filter.value) ? 'multiple' : undefined}
|
||||
showSearch
|
||||
// allowClear
|
||||
/>
|
||||
) : entityAlias &&
|
||||
['歌曲', '艺人'].includes(entityAlias) &&
|
||||
filter.bizName?.includes('_id') ? (
|
||||
<SwicthEntity
|
||||
entityName={filter.value}
|
||||
chatContext={chatContext}
|
||||
onSwitchEntity={onSwitchEntity}
|
||||
/>
|
||||
) : (
|
||||
<span className={`${prefixCls}-filter-value`}>{filter.value}</span>
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { AGG_TYPE_MAP, PREFIX_CLS } from '../../common/constants';
|
||||
import { ChatContextType, FilterItemType } from '../../common/type';
|
||||
import { ChatContextType, DateInfoType, EntityInfoType, FilterItemType } from '../../common/type';
|
||||
import { DatePicker } from 'antd';
|
||||
import { CheckCircleFilled } from '@ant-design/icons';
|
||||
import classNames from 'classnames';
|
||||
import SwicthEntity from './SwitchEntity';
|
||||
import Loading from './Loading';
|
||||
import FilterItem from './FilterItem';
|
||||
import moment from 'moment';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
type Props = {
|
||||
parseLoading: boolean;
|
||||
parseInfoOptions: ChatContextType[];
|
||||
parseTip: string;
|
||||
currentParseInfo?: ChatContextType;
|
||||
agentId?: number;
|
||||
dimensionFilters: FilterItemType[];
|
||||
dateInfo: DateInfoType;
|
||||
entityInfo: EntityInfoType;
|
||||
onSelectParseInfo: (parseInfo: ChatContextType) => void;
|
||||
onSwitchEntity: (entityId: string) => void;
|
||||
onFiltersChange: (filters: FilterItemType[]) => void;
|
||||
onDateInfoChange: (dateRange: any) => void;
|
||||
};
|
||||
|
||||
const MAX_OPTION_VALUES_COUNT = 2;
|
||||
@@ -24,16 +31,18 @@ const ParseTip: React.FC<Props> = ({
|
||||
parseInfoOptions,
|
||||
parseTip,
|
||||
currentParseInfo,
|
||||
agentId,
|
||||
dimensionFilters,
|
||||
dateInfo,
|
||||
entityInfo,
|
||||
onSelectParseInfo,
|
||||
onSwitchEntity,
|
||||
onFiltersChange,
|
||||
onDateInfoChange,
|
||||
}) => {
|
||||
const prefixCls = `${PREFIX_CLS}-item`;
|
||||
|
||||
const getNode = (tipTitle: ReactNode, tipNode?: ReactNode, parseSucceed?: boolean) => {
|
||||
const contentContainerClass = classNames(`${prefixCls}-content-container`, {
|
||||
[`${prefixCls}-content-container-succeed`]: parseSucceed,
|
||||
});
|
||||
return (
|
||||
<div className={`${prefixCls}-parse-tip`}>
|
||||
<div className={`${prefixCls}-title-bar`}>
|
||||
@@ -43,7 +52,7 @@ const ParseTip: React.FC<Props> = ({
|
||||
{!tipNode && <Loading />}
|
||||
</div>
|
||||
</div>
|
||||
{tipNode && <div className={contentContainerClass}>{tipNode}</div>}
|
||||
{tipNode && <div className={`${prefixCls}-content-container`}>{tipNode}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -60,22 +69,38 @@ const ParseTip: React.FC<Props> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const getTipNode = (parseInfo: ChatContextType, isOptions?: boolean, index?: number) => {
|
||||
const {
|
||||
modelName,
|
||||
dateInfo,
|
||||
dimensionFilters,
|
||||
dimensions,
|
||||
metrics,
|
||||
aggType,
|
||||
queryMode,
|
||||
properties,
|
||||
entity,
|
||||
elementMatches,
|
||||
nativeQuery,
|
||||
} = parseInfo || {};
|
||||
const {
|
||||
modelId,
|
||||
modelName,
|
||||
dimensions,
|
||||
metrics,
|
||||
aggType,
|
||||
queryMode,
|
||||
properties,
|
||||
entity,
|
||||
// entityInfo,
|
||||
elementMatches,
|
||||
nativeQuery,
|
||||
} = currentParseInfo || {};
|
||||
|
||||
const { startDate, endDate } = dateInfo || {};
|
||||
const { type } = properties || {};
|
||||
const entityAlias = entity?.alias?.[0]?.split('.')?.[0];
|
||||
const entityName = elementMatches?.find(item => item.element?.type === 'ID')?.element.name;
|
||||
|
||||
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 dimensionItems = dimensions?.filter(item => item.type === 'DIMENSION');
|
||||
const metric = metrics?.[0];
|
||||
|
||||
@@ -90,15 +115,7 @@ const ParseTip: React.FC<Props> = ({
|
||||
queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${prefixCls}-tip-content`}
|
||||
onClick={() => {
|
||||
if (isOptions && currentParseInfo === undefined) {
|
||||
onSelectParseInfo(parseInfo);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{index !== undefined && <div>{index + 1}.</div>}
|
||||
<div className={`${prefixCls}-tip-content`}>
|
||||
{!!agentType && queryMode !== 'DSL' ? (
|
||||
<div className={`${prefixCls}-tip-item`}>
|
||||
将由{agentType === 'plugin' ? '插件' : '内置'}工具
|
||||
@@ -112,37 +129,23 @@ const ParseTip: React.FC<Props> = ({
|
||||
!!entityName ? (
|
||||
<div className={`${prefixCls}-tip-item`}>
|
||||
<div className={`${prefixCls}-tip-item-name`}>{entityAlias}:</div>
|
||||
{!isOptions && (entityAlias === '歌曲' || entityAlias === '艺人') ? (
|
||||
<SwicthEntity
|
||||
entityName={entityName}
|
||||
chatContext={parseInfo}
|
||||
onSwitchEntity={onSwitchEntity}
|
||||
/>
|
||||
) : (
|
||||
<div className={itemValueClass}>{entityName}</div>
|
||||
)}
|
||||
<div className={itemValueClass}>{entityName}</div>
|
||||
</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 && (
|
||||
<div className={`${prefixCls}-tip-item`}>
|
||||
<div className={`${prefixCls}-tip-item-name`}>指标:</div>
|
||||
<div className={itemValueClass}>{metric.name}</div>
|
||||
</div>
|
||||
)}
|
||||
{!isOptions && (
|
||||
<div className={`${prefixCls}-tip-item`}>
|
||||
<div className={`${prefixCls}-tip-item-name`}>数据时间:</div>
|
||||
<div className={itemValueClass}>
|
||||
{startDate === endDate ? startDate : `${startDate} ~ ${endDate}`}
|
||||
{!queryMode?.includes('ENTITY') &&
|
||||
metric &&
|
||||
!dimensions?.some(item => item.bizName?.includes('_id')) && (
|
||||
<div className={`${prefixCls}-tip-item`}>
|
||||
<div className={`${prefixCls}-tip-item-name`}>指标:</div>
|
||||
<div className={itemValueClass}>{metric.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{['METRIC_GROUPBY', 'METRIC_ORDERBY', 'ENTITY_DETAIL', 'DSL'].includes(queryMode) &&
|
||||
)}
|
||||
{['METRIC_GROUPBY', 'METRIC_ORDERBY', 'ENTITY_DETAIL', 'DSL'].includes(queryMode!) &&
|
||||
fields &&
|
||||
fields.length > 0 && (
|
||||
<div className={`${prefixCls}-tip-item`}>
|
||||
@@ -166,7 +169,10 @@ const ParseTip: React.FC<Props> = ({
|
||||
</div>
|
||||
)}
|
||||
{queryMode !== 'ENTITY_ID' &&
|
||||
entityDimensions &&
|
||||
entityDimensions?.length > 0 &&
|
||||
!dimensions?.some(item => item.bizName?.includes('_id')) &&
|
||||
entityDimensions.some(dimension => dimension.value != null) &&
|
||||
entityDimensions.map(dimension => (
|
||||
<div className={`${prefixCls}-tip-item`} key={dimension.itemId}>
|
||||
<div className={`${prefixCls}-tip-item-name`}>{dimension.name}:</div>
|
||||
@@ -185,49 +191,38 @@ const ParseTip: React.FC<Props> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const parseInfo = parseInfoOptions[0] || {};
|
||||
const { modelId, properties, entity, entityInfo, elementMatches, queryMode, dimensionFilters } =
|
||||
parseInfo || {};
|
||||
|
||||
const { type } = properties || {};
|
||||
const entityAlias = entity?.alias?.[0]?.split('.')?.[0];
|
||||
const entityName = elementMatches?.find(item => item.element?.type === 'ID')?.element.name;
|
||||
|
||||
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 getFilterContent = (filters: any) => {
|
||||
const itemValueClass = `${prefixCls}-tip-item-value`;
|
||||
const { startDate, endDate } = dateInfo || {};
|
||||
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} ` : ':'}
|
||||
<div className={`${prefixCls}-tip-item-option`}>
|
||||
<span className={`${prefixCls}-tip-item-filter-name`}>数据时间:</span>
|
||||
{dimensions?.some(item => item.bizName?.includes('_id')) ? (
|
||||
<span className={itemValueClass}>
|
||||
{startDate === endDate ? startDate : `${startDate} ~ ${endDate}`}
|
||||
</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>
|
||||
) : (
|
||||
<RangePicker
|
||||
className={`${prefixCls}-range-picker`}
|
||||
value={[moment(startDate), moment(endDate)]}
|
||||
onChange={onDateInfoChange}
|
||||
allowClear={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{filters?.map((filter: any) => (
|
||||
<FilterItem
|
||||
modelId={modelId!}
|
||||
filters={dimensionFilters}
|
||||
filter={filter}
|
||||
chatContext={currentParseInfo!}
|
||||
entityAlias={entityAlias}
|
||||
agentId={agentId}
|
||||
onFiltersChange={onFiltersChange}
|
||||
onSwitchEntity={onSwitchEntity}
|
||||
key={filter.name}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
@@ -244,27 +239,34 @@ const ParseTip: React.FC<Props> = ({
|
||||
|
||||
const tipNode = (
|
||||
<div className={`${prefixCls}-tip`}>
|
||||
{getTipNode(parseInfo)}
|
||||
{[
|
||||
'METRIC_FILTER',
|
||||
'METRIC_ENTITY',
|
||||
'ENTITY_DETAIL',
|
||||
'ENTITY_LIST_FILTER',
|
||||
'ENTITY_ID',
|
||||
'DSL',
|
||||
].includes(queryMode) &&
|
||||
dimensionFilters &&
|
||||
dimensionFilters?.length > 0 &&
|
||||
getFiltersNode()}
|
||||
{getTipNode()}
|
||||
{getFiltersNode()}
|
||||
{(!type || queryMode === 'DSL') && entityAlias && entityAlias !== '厂牌' && entityName && (
|
||||
<div className={`${prefixCls}-switch-entity-tip`}>
|
||||
(如未匹配到相关{entityAlias},可点击{entityAlias === '艺人' ? '歌手' : entityAlias}ID切换)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return getNode(
|
||||
<div className={`${prefixCls}-title-bar`}>
|
||||
意图解析
|
||||
{(!type || queryMode === 'DSL') && entityAlias && entityAlias !== '厂牌' && entityName && (
|
||||
<div className={`${prefixCls}-switch-entity-tip`}>
|
||||
(如果未匹配到您查询的{entityAlias},可点击{entityAlias}名切换)
|
||||
<div>意图解析{parseInfoOptions?.length > 1 ? ':' : ''}</div>
|
||||
{parseInfoOptions?.length > 1 && (
|
||||
<div className={`${prefixCls}-content-options`}>
|
||||
{parseInfoOptions.map((parseInfo, index) => (
|
||||
<div
|
||||
className={`${prefixCls}-content-option ${
|
||||
parseInfo.id === currentParseInfo?.id ? `${prefixCls}-content-option-active` : ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
onSelectParseInfo(parseInfo);
|
||||
}}
|
||||
key={parseInfo.id}
|
||||
>
|
||||
解析{index + 1}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
|
||||
@@ -1,24 +1,44 @@
|
||||
import { CheckCircleFilled, DownOutlined, UpOutlined } from '@ant-design/icons';
|
||||
import { CheckCircleFilled, DownOutlined, LoadingOutlined, UpOutlined } from '@ant-design/icons';
|
||||
import { PREFIX_CLS } from '../../common/constants';
|
||||
import { SimilarQuestionType } from '../../common/type';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { querySimilarQuestions } from '../../service';
|
||||
|
||||
type Props = {
|
||||
similarQuestions: SimilarQuestionType[];
|
||||
// similarQuestions: SimilarQuestionType[];
|
||||
queryText: string;
|
||||
agentId?: number;
|
||||
defaultExpanded?: boolean;
|
||||
onSelectQuestion: (question: SimilarQuestionType) => void;
|
||||
};
|
||||
|
||||
const SimilarQuestions: React.FC<Props> = ({
|
||||
similarQuestions,
|
||||
// similarQuestions,
|
||||
queryText,
|
||||
agentId,
|
||||
defaultExpanded,
|
||||
onSelectQuestion,
|
||||
}) => {
|
||||
const [similarQuestions, setSimilarQuestions] = useState<SimilarQuestionType[]>([]);
|
||||
const [expanded, setExpanded] = useState(defaultExpanded || false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const tipPrefixCls = `${PREFIX_CLS}-item`;
|
||||
const prefixCls = `${PREFIX_CLS}-similar-questions`;
|
||||
|
||||
const initData = async () => {
|
||||
setLoading(true);
|
||||
const res = await querySimilarQuestions(queryText, agentId);
|
||||
setLoading(false);
|
||||
setSimilarQuestions(res.data || []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (expanded && similarQuestions?.length === 0) {
|
||||
initData();
|
||||
}
|
||||
}, [expanded]);
|
||||
|
||||
const onToggleExpanded = () => {
|
||||
setExpanded(!expanded);
|
||||
};
|
||||
@@ -30,14 +50,14 @@ const SimilarQuestions: React.FC<Props> = ({
|
||||
<div className={`${tipPrefixCls}-step-title`}>
|
||||
推荐相似问题
|
||||
<span className={`${prefixCls}-toggle-expand-btn`} onClick={onToggleExpanded}>
|
||||
{expanded ? <UpOutlined /> : <DownOutlined />}
|
||||
{loading ? <LoadingOutlined /> : expanded ? <UpOutlined /> : <DownOutlined />}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={prefixCls}>
|
||||
{expanded && (
|
||||
<div className={`${prefixCls}-content`}>
|
||||
{similarQuestions.slice(0, 5).map((question, index) => {
|
||||
{similarQuestions?.slice(0, 5).map((question, index) => {
|
||||
return (
|
||||
<div
|
||||
className={`${prefixCls}-question`}
|
||||
|
||||
@@ -36,18 +36,18 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => {
|
||||
<div className={`${tipPrefixCls}-title-bar`}>
|
||||
<CheckCircleFilled className={`${tipPrefixCls}-step-icon`} />
|
||||
<div className={`${tipPrefixCls}-step-title`}>
|
||||
SQL生成
|
||||
SQL生成:
|
||||
{sqlType && (
|
||||
<span className={`${prefixCls}-toggle-expand-btn`} onClick={onCollapse}>
|
||||
<UpOutlined />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${prefixCls}-sql-options`}>
|
||||
<div className={`${tipPrefixCls}-content-options`}>
|
||||
{sqlInfo.llmParseSql && (
|
||||
<div
|
||||
className={`${prefixCls}-sql-option ${
|
||||
sqlType === 'llmParseSql' ? `${prefixCls}-sql-option-active` : ''
|
||||
className={`${tipPrefixCls}-content-option ${
|
||||
sqlType === 'llmParseSql' ? `${tipPrefixCls}-content-option-active` : ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSqlType(sqlType === 'llmParseSql' ? '' : 'llmParseSql');
|
||||
@@ -58,8 +58,8 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => {
|
||||
)}
|
||||
{sqlInfo.logicSql && (
|
||||
<div
|
||||
className={`${prefixCls}-sql-option ${
|
||||
sqlType === 'logicSql' ? `${prefixCls}-sql-option-active` : ''
|
||||
className={`${tipPrefixCls}-content-option ${
|
||||
sqlType === 'logicSql' ? `${tipPrefixCls}-content-option-active` : ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSqlType(sqlType === 'logicSql' ? '' : 'logicSql');
|
||||
@@ -70,8 +70,8 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => {
|
||||
)}
|
||||
{sqlInfo.querySql && (
|
||||
<div
|
||||
className={`${prefixCls}-sql-option ${
|
||||
sqlType === 'querySql' ? `${prefixCls}-sql-option-active` : ''
|
||||
className={`${tipPrefixCls}-content-option ${
|
||||
sqlType === 'querySql' ? `${tipPrefixCls}-content-option-active` : ''
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSqlType(sqlType === 'querySql' ? '' : 'querySql');
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {
|
||||
ChatContextType,
|
||||
DateInfoType,
|
||||
EntityInfoType,
|
||||
FilterItemType,
|
||||
MsgDataType,
|
||||
ParseStateEnum,
|
||||
SimilarQuestionType,
|
||||
} from '../../common/type';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { chatExecute, chatParse, queryData, switchEntity } from '../../service';
|
||||
import { chatExecute, chatParse, queryData, queryEntityInfo, switchEntity } from '../../service';
|
||||
import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/constants';
|
||||
import IconFont from '../IconFont';
|
||||
import ParseTip from './ParseTip';
|
||||
@@ -16,6 +18,7 @@ import classNames from 'classnames';
|
||||
import Tools from '../Tools';
|
||||
import SqlItem from './SqlItem';
|
||||
import SimilarQuestionItem from './SimilarQuestionItem';
|
||||
import moment from 'moment';
|
||||
|
||||
type Props = {
|
||||
msg: string;
|
||||
@@ -26,9 +29,10 @@ type Props = {
|
||||
isLastMessage?: boolean;
|
||||
msgData?: MsgDataType;
|
||||
triggerResize?: boolean;
|
||||
parseOptions?: ChatContextType[];
|
||||
isDeveloper?: boolean;
|
||||
integrateSystem?: string;
|
||||
executeItemNode?: React.ReactNode;
|
||||
renderCustomExecuteNode?: boolean;
|
||||
onMsgDataLoaded?: (data: MsgDataType, valid: boolean) => void;
|
||||
onUpdateMessageScroll?: () => void;
|
||||
onSendMsg?: (msg: string) => void;
|
||||
@@ -43,9 +47,10 @@ const ChatItem: React.FC<Props> = ({
|
||||
isLastMessage,
|
||||
triggerResize,
|
||||
msgData,
|
||||
parseOptions,
|
||||
isDeveloper,
|
||||
integrateSystem,
|
||||
executeItemNode,
|
||||
renderCustomExecuteNode,
|
||||
onMsgDataLoaded,
|
||||
onUpdateMessageScroll,
|
||||
onSendMsg,
|
||||
@@ -53,13 +58,15 @@ const ChatItem: React.FC<Props> = ({
|
||||
const [data, setData] = useState<MsgDataType>();
|
||||
const [parseLoading, setParseLoading] = useState(false);
|
||||
const [parseInfo, setParseInfo] = useState<ChatContextType>();
|
||||
const [parseInfoOptions, setParseInfoOptions] = useState<ChatContextType[]>(parseOptions || []);
|
||||
const [parseInfoOptions, setParseInfoOptions] = useState<ChatContextType[]>([]);
|
||||
const [parseTip, setParseTip] = useState('');
|
||||
const [executeLoading, setExecuteLoading] = useState(false);
|
||||
const [executeTip, setExecuteTip] = useState('');
|
||||
const [executeMode, setExecuteMode] = useState(false);
|
||||
const [entitySwitchLoading, setEntitySwitchLoading] = useState(false);
|
||||
const [similarQuestions, setSimilarQuestions] = useState<SimilarQuestionType[]>([]);
|
||||
const [dimensionFilters, setDimensionFilters] = useState<FilterItemType[]>([]);
|
||||
const [dateInfo, setDateInfo] = useState<DateInfoType>({} as DateInfoType);
|
||||
const [entityInfo, setEntityInfo] = useState<EntityInfoType>({} as EntityInfoType);
|
||||
|
||||
const [chartIndex, setChartIndex] = useState(0);
|
||||
|
||||
@@ -95,15 +102,13 @@ const ChatItem: React.FC<Props> = ({
|
||||
const res: any = await chatExecute(msg, conversationId!, parseInfoValue);
|
||||
setExecuteLoading(false);
|
||||
const valid = updateData(res);
|
||||
if (onMsgDataLoaded) {
|
||||
onMsgDataLoaded(
|
||||
{
|
||||
...res.data,
|
||||
chatContext: parseInfoValue,
|
||||
},
|
||||
valid
|
||||
);
|
||||
}
|
||||
onMsgDataLoaded?.(
|
||||
{
|
||||
...res.data,
|
||||
chatContext: parseInfoValue,
|
||||
},
|
||||
valid
|
||||
);
|
||||
} catch (e) {
|
||||
setExecuteLoading(false);
|
||||
setExecuteTip(SEARCH_EXCEPTION_TIP);
|
||||
@@ -115,8 +120,7 @@ const ChatItem: React.FC<Props> = ({
|
||||
const parseData: any = await chatParse(msg, conversationId, modelId, agentId, filter);
|
||||
setParseLoading(false);
|
||||
const { code, data } = parseData || {};
|
||||
const { state, selectedParses, queryId, similarSolvedQuery } = data || {};
|
||||
setSimilarQuestions(similarSolvedQuery || []);
|
||||
const { state, selectedParses, candidateParses, queryId } = data || {};
|
||||
if (
|
||||
code !== 200 ||
|
||||
state === ParseStateEnum.FAILED ||
|
||||
@@ -126,27 +130,33 @@ const ChatItem: React.FC<Props> = ({
|
||||
setParseTip(PARSE_ERROR_TIP);
|
||||
return;
|
||||
}
|
||||
if (onUpdateMessageScroll) {
|
||||
onUpdateMessageScroll();
|
||||
}
|
||||
const parseInfos = selectedParses.map((item: any) => ({
|
||||
...item,
|
||||
queryId,
|
||||
}));
|
||||
onUpdateMessageScroll?.();
|
||||
const parseInfos = selectedParses
|
||||
.concat(candidateParses || [])
|
||||
.slice(0, 5)
|
||||
.map((item: any) => ({
|
||||
...item,
|
||||
queryId,
|
||||
}));
|
||||
setParseInfoOptions(parseInfos || []);
|
||||
const parseInfoValue = parseInfos[0];
|
||||
setParseInfo(parseInfoValue);
|
||||
setEntityInfo(parseInfoValue.entityInfo || {});
|
||||
setDimensionFilters(parseInfoValue?.dimensionFilters || []);
|
||||
setDateInfo(parseInfoValue?.dateInfo);
|
||||
onExecute(parseInfoValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data !== undefined || parseOptions !== undefined || executeTip !== '' || parseLoading) {
|
||||
if (data !== undefined || executeTip !== '' || parseLoading) {
|
||||
return;
|
||||
}
|
||||
if (msgData) {
|
||||
const parseInfoValue = { ...msgData.chatContext, queryId: msgData.queryId };
|
||||
setParseInfoOptions([parseInfoValue]);
|
||||
setParseInfo(parseInfoValue);
|
||||
setDimensionFilters(msgData.chatContext?.dimensionFilters || []);
|
||||
setDateInfo(msgData.chatContext?.dateInfo);
|
||||
setExecuteMode(true);
|
||||
updateData({ code: 200, data: msgData, msg: 'success' });
|
||||
} else if (msg) {
|
||||
@@ -159,14 +169,31 @@ const ChatItem: React.FC<Props> = ({
|
||||
const res = await switchEntity(entityId, data?.chatContext?.modelId, conversationId || 0);
|
||||
setEntitySwitchLoading(false);
|
||||
setData(res.data);
|
||||
const { chatContext } = res.data;
|
||||
setParseInfo(chatContext);
|
||||
setParseInfoOptions([chatContext]);
|
||||
const { chatContext, entityInfo } = res.data;
|
||||
const chatContextValue = { ...(chatContext || {}), queryId: parseInfo?.queryId };
|
||||
setParseInfo(chatContextValue);
|
||||
setEntityInfo(entityInfo);
|
||||
setDimensionFilters(chatContextValue?.dimensionFilters || []);
|
||||
setDateInfo(chatContextValue?.dateInfo);
|
||||
};
|
||||
|
||||
const onFiltersChange = async (dimensionFilters: FilterItemType[]) => {
|
||||
const onFiltersChange = (dimensionFilters: FilterItemType[]) => {
|
||||
setDimensionFilters(dimensionFilters);
|
||||
};
|
||||
|
||||
const onDateInfoChange = (dateRange: any) => {
|
||||
setDateInfo({
|
||||
...(dateInfo || {}),
|
||||
startDate: moment(dateRange[0]).format('YYYY-MM-DD'),
|
||||
endDate: moment(dateRange[1]).format('YYYY-MM-DD'),
|
||||
dateMode: 'BETWEEN',
|
||||
unit: 0,
|
||||
});
|
||||
};
|
||||
|
||||
const onRefresh = async () => {
|
||||
setEntitySwitchLoading(true);
|
||||
const { dimensions, metrics, dateInfo, id, queryId } = parseInfoOptions[0] || {};
|
||||
const { dimensions, metrics, id, queryId } = parseInfo || {};
|
||||
const chatContextValue = {
|
||||
dimensions,
|
||||
metrics,
|
||||
@@ -177,18 +204,33 @@ const ChatItem: React.FC<Props> = ({
|
||||
};
|
||||
const res: any = await queryData(chatContextValue);
|
||||
setEntitySwitchLoading(false);
|
||||
const resChatContext = res.data?.chatContext;
|
||||
setData({ ...(res.data || {}), chatContext: resChatContext || chatContextValue });
|
||||
setParseInfo(resChatContext || chatContextValue);
|
||||
setParseInfoOptions([resChatContext || chatContextValue]);
|
||||
if (res.code === 200) {
|
||||
const resChatContext = res.data?.chatContext;
|
||||
const contextValue = { ...(resChatContext || chatContextValue), queryId };
|
||||
const dataValue = { ...res.data, chatContext: contextValue };
|
||||
if (onMsgDataLoaded) {
|
||||
onMsgDataLoaded(dataValue, true);
|
||||
}
|
||||
setData(dataValue);
|
||||
setParseInfo(contextValue);
|
||||
}
|
||||
};
|
||||
|
||||
const getEntityInfo = async (parseInfoValue: ChatContextType) => {
|
||||
const res = await queryEntityInfo(parseInfoValue.queryId, parseInfoValue.id);
|
||||
setEntityInfo(res.data);
|
||||
};
|
||||
|
||||
const onSelectParseInfo = async (parseInfoValue: ChatContextType) => {
|
||||
setParseInfo(parseInfoValue);
|
||||
onExecute(parseInfoValue);
|
||||
if (onUpdateMessageScroll) {
|
||||
onUpdateMessageScroll();
|
||||
setDimensionFilters(parseInfoValue.dimensionFilters || []);
|
||||
setDateInfo(parseInfoValue.dateInfo);
|
||||
if (parseInfoValue.entityInfo) {
|
||||
setEntityInfo(parseInfoValue.entityInfo);
|
||||
} else {
|
||||
getEntityInfo(parseInfoValue);
|
||||
}
|
||||
onUpdateMessageScroll?.();
|
||||
};
|
||||
|
||||
const onSelectQuestion = (question: SimilarQuestionType) => {
|
||||
@@ -205,36 +247,29 @@ const ChatItem: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
{!isMobile && <IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />}
|
||||
{!isMobile && integrateSystem !== 'wiki' && (
|
||||
<IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />
|
||||
)}
|
||||
<div className={isMobile ? `${prefixCls}-mobile-msg-card` : `${prefixCls}-msg-card`}>
|
||||
<div className={contentClass}>
|
||||
<ParseTip
|
||||
parseLoading={parseLoading}
|
||||
parseInfoOptions={parseOptions || parseInfoOptions.slice(0, 1)}
|
||||
parseInfoOptions={parseInfoOptions}
|
||||
parseTip={parseTip}
|
||||
currentParseInfo={parseInfo}
|
||||
agentId={agentId}
|
||||
dimensionFilters={dimensionFilters}
|
||||
dateInfo={dateInfo}
|
||||
entityInfo={entityInfo}
|
||||
onSelectParseInfo={onSelectParseInfo}
|
||||
onSwitchEntity={onSwitchEntity}
|
||||
onFiltersChange={onFiltersChange}
|
||||
onDateInfoChange={onDateInfoChange}
|
||||
/>
|
||||
{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}
|
||||
/>
|
||||
{parseInfo?.sqlInfo && isDeveloper && integrateSystem !== 'c2' && (
|
||||
<SqlItem integrateSystem={integrateSystem} sqlInfo={parseInfo.sqlInfo} />
|
||||
)}
|
||||
<ExecuteItem
|
||||
queryId={parseInfo?.queryId}
|
||||
@@ -244,12 +279,27 @@ const ChatItem: React.FC<Props> = ({
|
||||
chartIndex={chartIndex}
|
||||
data={data}
|
||||
triggerResize={triggerResize}
|
||||
executeItemNode={executeItemNode}
|
||||
renderCustomExecuteNode={renderCustomExecuteNode}
|
||||
onRefresh={onRefresh}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{(parseTip !== '' || (executeMode && !executeLoading)) && integrateSystem !== 'c2' && (
|
||||
<SimilarQuestionItem
|
||||
queryText={msg || msgData?.queryText || ''}
|
||||
agentId={agentId}
|
||||
defaultExpanded={parseTip !== '' || executeTip !== '' || integrateSystem === 'wiki'}
|
||||
onSelectQuestion={onSelectQuestion}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!isMetricCard && data && (
|
||||
<Tools data={data} scoreValue={undefined} isLastMessage={isLastMessage} />
|
||||
<Tools
|
||||
queryId={parseInfo?.queryId || 0}
|
||||
scoreValue={undefined}
|
||||
isLastMessage={isLastMessage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -52,6 +52,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-content-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 13px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
&-content-option {
|
||||
border-radius: 4px;
|
||||
padding: 0 4px;
|
||||
font-weight: normal;
|
||||
color: var(--text-color-third);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--chat-blue);
|
||||
}
|
||||
}
|
||||
|
||||
&-content-option-active {
|
||||
color: #fff !important;
|
||||
background-color: var(--chat-blue);
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -93,12 +117,34 @@
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.ant-picker {
|
||||
background-color: #f5f8fb !important;
|
||||
border-color: #ececec !important;
|
||||
}
|
||||
|
||||
.ant-picker-input > input {
|
||||
color: var(--chat-blue);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-title-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 10px;
|
||||
}
|
||||
|
||||
&-step-title {
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
&-reload {
|
||||
margin-left: 2px;
|
||||
font-weight: normal;
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
&-step-icon {
|
||||
color: var(--green);
|
||||
font-size: 16px;
|
||||
@@ -106,10 +152,7 @@
|
||||
|
||||
&-content-container {
|
||||
margin: 2px 0 2px 7px;
|
||||
padding: 6px 0 4px 18px;
|
||||
}
|
||||
|
||||
&-content-container-succeed {
|
||||
padding: 10px 0 4px 18px;
|
||||
border-left: 1px solid var(--green);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
@@ -127,6 +170,7 @@
|
||||
column-gap: 6px;
|
||||
color: var(--text-color-fourth);
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
&-switch-entity {
|
||||
@@ -213,6 +257,8 @@
|
||||
&-tip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
&-tip-item-content {
|
||||
@@ -225,10 +271,12 @@
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 12px;
|
||||
row-gap: 6px;
|
||||
}
|
||||
|
||||
&-tip-item-filter-name {
|
||||
color: var(--text-color-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-mode-name {
|
||||
@@ -241,10 +289,6 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-tip-item-option {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-entity-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -305,13 +349,35 @@
|
||||
}
|
||||
|
||||
.@{filter-item-prefix-cls} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
|
||||
&-filter-name {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
&-select-control {
|
||||
min-width: 120px;
|
||||
background-color: #f5f8fb;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ant-select-selection-item {
|
||||
&-operator-control {
|
||||
min-width: 80px;
|
||||
border-radius: 6px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&-input-number-control {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.ant-select-selector, .ant-input-number-input {
|
||||
background-color: #f5f8fb !important;
|
||||
border-color: #ececec !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-item, .ant-input-number-input {
|
||||
color: var(--chat-blue);
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -334,34 +400,11 @@
|
||||
overflow: auto;
|
||||
|
||||
&-toggle-expand-btn {
|
||||
margin-left: 4px;
|
||||
color: var(--text-color-fourth);
|
||||
font-size: 12px;
|
||||
margin-right: 10px;
|
||||
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;
|
||||
@@ -373,7 +416,7 @@
|
||||
|
||||
&-copy-btn {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
top: 24px;
|
||||
right: 20px;
|
||||
background: transparent !important;
|
||||
border: 0 !important;
|
||||
@@ -390,7 +433,6 @@
|
||||
position: relative;
|
||||
margin: 2px 0 2px 7px;
|
||||
padding: 2px 0 8px 18px;
|
||||
border-left: 1px solid var(--green);
|
||||
overflow: auto;
|
||||
|
||||
&-toggle-expand-btn {
|
||||
@@ -403,7 +445,7 @@
|
||||
&-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 12px;
|
||||
row-gap: 8px;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
@@ -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`}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MsgDataType } from '../../../common/type';
|
||||
import { CLS_PREFIX } from '../../../common/constants';
|
||||
import ApplyAuth from '../ApplyAuth';
|
||||
import { SizeType } from 'antd/es/config-provider/SizeContext';
|
||||
import moment from 'moment';
|
||||
|
||||
type Props = {
|
||||
data: MsgDataType;
|
||||
|
||||
@@ -86,7 +86,6 @@
|
||||
|
||||
.@{table-prefix-cls}-formatted-value {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.ant-table-thead .ant-table-cell {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { CLS_PREFIX } from '../../../common/constants';
|
||||
import { MsgDataType } from '../../../common/type';
|
||||
import { getToken, isProd } from '../../../utils/utils';
|
||||
|
||||
@@ -14,8 +13,6 @@ const WebPage: React.FC<Props> = ({ id, data }) => {
|
||||
const [pluginUrl, setPluginUrl] = useState('');
|
||||
const [height, setHeight] = useState(DEFAULT_HEIGHT);
|
||||
|
||||
const prefixCls = `${CLS_PREFIX}-web-page`;
|
||||
|
||||
const {
|
||||
name,
|
||||
webPage: { url, params },
|
||||
|
||||
@@ -129,7 +129,6 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
|
||||
const onLoadData = async (value: any) => {
|
||||
setLoading(true);
|
||||
const res: any = await queryData({
|
||||
// ...chatContext,
|
||||
queryId,
|
||||
parseId: chatContext.id,
|
||||
...value,
|
||||
@@ -200,7 +199,7 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
|
||||
|
||||
const existDrillDownDimension = queryMode.includes('METRIC') && !isText && !isEntityMode;
|
||||
|
||||
const isMultipleMetric = existDrillDownDimension && chatContext?.metrics?.length > 1;
|
||||
const isMultipleMetric = queryMode.includes('METRIC') && chatContext?.metrics?.length > 1;
|
||||
|
||||
return (
|
||||
<div className={chartMsgClass}>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import { CLS_PREFIX } from '../../common/constants';
|
||||
import { DrillDownDimensionType, FilterItemType } from '../../common/type';
|
||||
import { queryDrillDownDimensions } from '../../service';
|
||||
import { Dropdown } from 'antd';
|
||||
import { Dropdown, Menu } from 'antd';
|
||||
import { DownOutlined } from '@ant-design/icons';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -87,26 +87,27 @@ const DrillDownDimensions: React.FC<Props> = ({
|
||||
<div>
|
||||
<span>、</span>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: dimensions.slice(DEFAULT_DIMENSION_COUNT).map(dimension => {
|
||||
const itemNameClass = classNames({
|
||||
[`${prefixCls}-menu-item-active`]: drillDownDimension?.id === dimension.id,
|
||||
});
|
||||
return {
|
||||
label: (
|
||||
<span
|
||||
className={itemNameClass}
|
||||
onClick={() => {
|
||||
onSelectDimension(dimension);
|
||||
}}
|
||||
>
|
||||
{dimension.name}
|
||||
</span>
|
||||
),
|
||||
key: dimension.id,
|
||||
};
|
||||
}),
|
||||
}}
|
||||
overlay={
|
||||
<Menu>
|
||||
{dimensions.slice(DEFAULT_DIMENSION_COUNT).map(dimension => {
|
||||
const itemNameClass = classNames({
|
||||
[`${prefixCls}-menu-item-active`]: drillDownDimension?.id === dimension.id,
|
||||
});
|
||||
return (
|
||||
<Menu.Item key={dimension.id}>
|
||||
<span
|
||||
className={itemNameClass}
|
||||
onClick={() => {
|
||||
onSelectDimension(dimension);
|
||||
}}
|
||||
>
|
||||
{dimension.name}
|
||||
</span>
|
||||
</Menu.Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<span className={`${prefixCls}-content-item-name`}>更多</span>
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
import { isMobile } from '../../utils/utils';
|
||||
import { DislikeOutlined, LikeOutlined } from '@ant-design/icons';
|
||||
import { CLS_PREFIX } from '../../common/constants';
|
||||
import { MsgDataType } from '../../common/type';
|
||||
import { useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { updateQAFeedback } from '../../service';
|
||||
|
||||
type Props = {
|
||||
data: MsgDataType;
|
||||
queryId: number;
|
||||
scoreValue?: number;
|
||||
isLastMessage?: boolean;
|
||||
};
|
||||
|
||||
const Tools: React.FC<Props> = ({ data, scoreValue, isLastMessage }) => {
|
||||
const { queryResults, queryId, chatContext, queryMode } = data || {};
|
||||
const Tools: React.FC<Props> = ({ queryId, scoreValue, isLastMessage }) => {
|
||||
const [score, setScore] = useState(scoreValue || 0);
|
||||
|
||||
const prefixCls = `${CLS_PREFIX}-tools`;
|
||||
|
||||
const singleData = queryResults?.length === 1;
|
||||
const isMetricCard =
|
||||
queryMode.includes('METRIC') &&
|
||||
(singleData || chatContext?.dateInfo?.startDate === chatContext?.dateInfo?.endDate);
|
||||
|
||||
const like = () => {
|
||||
setScore(5);
|
||||
updateQAFeedback(queryId, 5);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Input } from 'antd';
|
||||
import styles from './style.module.less';
|
||||
import { useEffect, useState } from 'react';
|
||||
import ChatItem from '../components/ChatItem';
|
||||
import { queryContext, searchRecommend } from '../service';
|
||||
import { searchRecommend } from '../service';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chatDemo {
|
||||
height: 100%;
|
||||
.copilotDemo {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.chatDemo {
|
||||
height: 100vh;
|
||||
}
|
||||
@@ -35,6 +35,8 @@ export type {
|
||||
SendMsgParamsType,
|
||||
} from './common/type';
|
||||
|
||||
export { getHistoryMsg, searchRecommend, queryContext } from './service';
|
||||
export { searchRecommend } from './service';
|
||||
|
||||
export { saveConversation, getAllConversations } from './Chat/service';
|
||||
|
||||
export { setToken } from './utils/utils';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from './axiosInstance';
|
||||
import { ChatContextType, DrillDownDimensionType, HistoryType, MsgDataType, ParseDataType, SearchRecommendItem } from '../common/type';
|
||||
import { ChatContextType, DrillDownDimensionType, EntityInfoType, HistoryType, MsgDataType, ParseDataType, SearchRecommendItem } from '../common/type';
|
||||
|
||||
const DEFAULT_CHAT_ID = 0;
|
||||
|
||||
@@ -58,13 +58,6 @@ export function queryData(chatContext: Partial<ChatContextType>) {
|
||||
return axios.post<MsgDataType>(`${prefix}/chat/query/queryData`, chatContext);
|
||||
}
|
||||
|
||||
export function queryContext(queryText: string, chatId?: number) {
|
||||
return axios.post<ChatContextType>(`${prefix}/chat/query/queryContext`, {
|
||||
queryText,
|
||||
chatId: chatId || DEFAULT_CHAT_ID,
|
||||
});
|
||||
}
|
||||
|
||||
export function getHistoryMsg(current: number, chatId: number = DEFAULT_CHAT_ID, pageSize: number = 10) {
|
||||
return axios.post<HistoryType>(`${prefix}/chat/manage/pageQueryInfo?chatId=${chatId}`, {
|
||||
current,
|
||||
@@ -72,14 +65,6 @@ export function getHistoryMsg(current: number, chatId: number = DEFAULT_CHAT_ID,
|
||||
});
|
||||
}
|
||||
|
||||
export function saveConversation(chatName: string) {
|
||||
return axios.post<any>(`${prefix}/chat/manage/save?chatName=${chatName}`);
|
||||
}
|
||||
|
||||
export function getAllConversations() {
|
||||
return axios.get<any>(`${prefix}/chat/manage/getAll`);
|
||||
}
|
||||
|
||||
export function queryEntities(entityId: string | number, modelId: number) {
|
||||
return axios.post<any>(`${prefix}/chat/query/choice`, {
|
||||
entityId,
|
||||
@@ -95,6 +80,14 @@ export function queryDrillDownDimensions(modelId: number) {
|
||||
return axios.get<{ dimensions: DrillDownDimensionType[] }>(`${prefix}/chat/recommend/metric/${modelId}`);
|
||||
}
|
||||
|
||||
export function queryDimensionValues(modelId: number, bizName: string, value: string) {
|
||||
return axios.post<any>(`${prefix}/chat/query/queryDimensionValue`, { modelId, bizName, value});
|
||||
export function queryDimensionValues(modelId: number, bizName: string, agentId: number, elementID: number, value: string) {
|
||||
return axios.post<any>(`${prefix}/chat/query/queryDimensionValue`, { modelId, bizName, agentId, elementID, value});
|
||||
}
|
||||
|
||||
export function querySimilarQuestions(queryText: string, agentId?: number) {
|
||||
return axios.get<any>(`${prefix}/chat/manage/getSolvedQuery?queryText=${queryText}&agentId=${agentId}`);
|
||||
}
|
||||
|
||||
export function queryEntityInfo(queryId: number, parseId: number) {
|
||||
return axios.get<EntityInfoType>(`${prefix}/chat/query/getEntityInfo?queryId=${queryId}&parseId=${parseId}`)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '~antd/dist/antd.css';
|
||||
|
||||
@import './index.less';
|
||||
|
||||
@prefix-cls: ~'@{supersonic-chat-prefix}';
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution":"Node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"importHelpers": true
|
||||
"importHelpers": true,
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
||||
733
webapp/pnpm-lock.yaml
generated
733
webapp/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user