(feature)(webapp) add filter modify and similar questions (#213)

This commit is contained in:
williamhliu
2023-10-13 18:31:00 +08:00
committed by GitHub
parent ab19b18169
commit 767abc2b90
27 changed files with 799 additions and 795 deletions

View File

@@ -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",

View File

@@ -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>
); );
})} })}

View File

@@ -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]: '维度值',

View File

@@ -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}`);
}; };

View File

@@ -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;
}; };

View File

@@ -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[];
}; };

View File

@@ -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

View File

@@ -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>

View File

@@ -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>,

View File

@@ -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`}

View File

@@ -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');

View File

@@ -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>

View File

@@ -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;
} }

View File

@@ -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`}>

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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 },

View File

@@ -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}>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;

View File

@@ -10,6 +10,10 @@
box-sizing: border-box; box-sizing: border-box;
} }
.chatDemo { .copilotDemo {
height: 100%; padding: 20px;
} }
.chatDemo {
height: 100vh;
}

View File

@@ -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';

View File

@@ -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}`)
} }

View File

@@ -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}';

View File

@@ -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

File diff suppressed because it is too large Load Diff