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