(feature)(webapp) add display of time consumption at each stage (#331)

This commit is contained in:
williamhliu
2023-11-06 20:35:14 +08:00
committed by GitHub
parent e00b935c1f
commit 6c9983164e
21 changed files with 417 additions and 158 deletions

View File

@@ -76,6 +76,7 @@ const MessageContainer: React.FC<Props> = ({
score, score,
identityMsg, identityMsg,
parseInfos, parseInfos,
parseTimeCost,
msgData, msgData,
filters, filters,
} = msgItem; } = msgItem;
@@ -93,13 +94,13 @@ const MessageContainer: React.FC<Props> = ({
<ChatItem <ChatItem
msg={msgValue || msg || ''} msg={msgValue || msg || ''}
parseInfos={parseInfos} parseInfos={parseInfos}
parseTimeCostValue={parseTimeCost}
msgData={msgData} msgData={msgData}
conversationId={chatId} conversationId={chatId}
modelId={modelId} modelId={modelId}
agentId={agentId} agentId={agentId}
score={score} score={score}
filter={filters} filter={filters}
isLastMessage={index === messageList.length - 1}
triggerResize={triggerResize} triggerResize={triggerResize}
isDeveloper={isDeveloper} isDeveloper={isDeveloper}
integrateSystem={integrateSystem} integrateSystem={integrateSystem}

View File

@@ -21,7 +21,7 @@ import MobileAgents from './MobileAgents';
import { HistoryMsgItemType, MsgDataType, SendMsgParamsType } from '../common/type'; import { HistoryMsgItemType, MsgDataType, SendMsgParamsType } from '../common/type';
import { getHistoryMsg } from '../service'; import { getHistoryMsg } from '../service';
import ShowCase from '../ShowCase'; import ShowCase from '../ShowCase';
import { Modal } from 'antd'; import { Drawer, Modal } from 'antd';
type Props = { type Props = {
token?: string; token?: string;
@@ -176,6 +176,7 @@ const Chat: ForwardRefRenderFunction<any, Props> = (
type: MessageTypeEnum.QUESTION, type: MessageTypeEnum.QUESTION,
msg: item.queryText, msg: item.queryText,
parseInfos: item.parseInfos, parseInfos: item.parseInfos,
parseTimeCost: item.parseTimeCost,
msgData: item.queryResult, msgData: item.queryResult,
score: item.score, score: item.score,
agentId: currentAgent?.id, agentId: currentAgent?.id,
@@ -421,7 +422,22 @@ const Chat: ForwardRefRenderFunction<any, Props> = (
onCloseConversation={onCloseConversation} onCloseConversation={onCloseConversation}
ref={conversationRef} ref={conversationRef}
/> />
{currentAgent && ( {currentAgent &&
(isMobile ? (
<Drawer
title="showcase"
placement="bottom"
height="95%"
open={showCaseVisible}
className={styles.showCaseDrawer}
destroyOnClose
onClose={() => {
setShowCaseVisible(false);
}}
>
<ShowCase agentId={currentAgent.id} onSendMsg={onSendMsg} />
</Drawer>
) : (
<Modal <Modal
title="showcase" title="showcase"
width="98%" width="98%"
@@ -429,15 +445,18 @@ const Chat: ForwardRefRenderFunction<any, Props> = (
centered centered
footer={null} footer={null}
wrapClassName={styles.showCaseModal} wrapClassName={styles.showCaseModal}
destroyOnClose
onCancel={() => { onCancel={() => {
setShowCaseVisible(false); setShowCaseVisible(false);
}} }}
> >
<div className={styles.showCase}> <ShowCase
<ShowCase agentId={currentAgent.id} onSendMsg={onSendMsg} /> height="calc(100vh - 140px)"
</div> agentId={currentAgent.id}
onSendMsg={onSendMsg}
/>
</Modal> </Modal>
)} ))}
</div> </div>
<MobileAgents <MobileAgents
open={mobileAgentsVisible} open={mobileAgentsVisible}

View File

@@ -49,7 +49,7 @@
width: 600px; width: 600px;
margin-left: 16px; margin-left: 16px;
overflow: hidden; overflow: hidden;
color: var(--text-color-fourth); color: var(--text-color-third);
font-size: 12px; font-size: 12px;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -79,17 +79,30 @@
.showCaseModal { .showCaseModal {
:global { :global {
.ant-modal-content {
border-radius: 8px;
.ant-modal-header {
border-radius: 8px 8px 0 0;
}
.ant-modal-body { .ant-modal-body {
padding: 20px 0 !important; padding: 20px 0 !important;
} }
} }
} }
}
.showCase { .showCaseDrawer {
height: calc(100vh - 140px); :global {
padding: 0 20px; .ant-drawer-content {
overflow-y: auto; border-top-left-radius: 12px;
padding-bottom: 2px; border-top-right-radius: 12px;
.ant-drawer-body {
padding: 4px 0 !important;
}
}
}
} }
:global { :global {

View File

@@ -1,4 +1,4 @@
import { ChatContextType, MsgDataType, SendMsgParamsType } from "../common/type"; import { ChatContextType, MsgDataType, ParseTimeCostType, SendMsgParamsType } from "../common/type";
export enum MessageTypeEnum { export enum MessageTypeEnum {
TEXT = 'text', // 指标文本 TEXT = 'text', // 指标文本
@@ -23,6 +23,7 @@ export type MessageItem = {
agentId?: number; agentId?: number;
entityId?: string; entityId?: string;
parseInfos?: ChatContextType[]; parseInfos?: ChatContextType[];
parseTimeCost?: ParseTimeCostType;
msgData?: MsgDataType; msgData?: MsgDataType;
quote?: string; quote?: string;
score?: number; score?: number;

View File

@@ -1,54 +1,108 @@
import { useEffect, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import styles from './style.module.less'; import styles from './style.module.less';
import { ShowCaseMapType } from './type'; import { ShowCaseItemType } from './type';
import { queryShowCase } from './service'; import { queryShowCase } from './service';
import Text from '../Chat/components/Text'; import Text from '../Chat/components/Text';
import ChatItem from '../components/ChatItem'; import ChatItem from '../components/ChatItem';
import { HistoryMsgItemType } from '../common/type'; import { HistoryMsgItemType } from '../common/type';
import { Spin } from 'antd'; import { Spin } from 'antd';
import classNames from 'classnames';
import { isMobile } from '../utils/utils';
import { useThrottleFn } from 'ahooks';
type Props = { type Props = {
height?: number | string;
agentId: number; agentId: number;
onSendMsg?: (msg: string) => void; onSendMsg?: (msg: string) => void;
}; };
const ShowCase: React.FC<Props> = ({ agentId, onSendMsg }) => { const ShowCase: React.FC<Props> = ({ height, agentId, onSendMsg }) => {
const [showCaseMap, setShowCaseMap] = useState<ShowCaseMapType>({}); const [showCaseList, setShowCaseList] = useState<ShowCaseItemType[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [pageNo, setPageNo] = useState(1);
const updateData = async () => { const showcaseRef = useRef<any>(null);
const updateData = async (pageNoValue: number) => {
if (pageNoValue === 1) {
setLoading(true); setLoading(true);
const res = await queryShowCase(agentId, 1, 30); }
const res = await queryShowCase(agentId, pageNoValue, isMobile ? 10 : 20);
if (pageNoValue === 1) {
setLoading(false); setLoading(false);
setShowCaseMap(res.data.showCaseMap); }
const showCaseMapRes: any = res.data.showCaseMap;
const list = Object.keys(showCaseMapRes)
.reduce((result: ShowCaseItemType[], key: string) => {
result.push({ msgList: showCaseMapRes[key], caseId: key });
return result;
}, [])
.sort((a, b) => {
return (b.msgList?.[0]?.score || 3) - (a.msgList?.[0]?.score || 3);
});
setShowCaseList(pageNoValue === 1 ? list : [...showCaseList, ...list]);
}; };
const { run: handleScroll } = useThrottleFn(
e => {
const bottom =
e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight ||
e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight + 0.5;
if (bottom) {
updateData(pageNo + 1);
setPageNo(pageNo + 1);
}
},
{
leading: true,
trailing: true,
wait: 200,
}
);
useEffect(() => {
if (isMobile) {
return;
}
const el = showcaseRef.current;
el?.addEventListener('scroll', handleScroll);
return () => {
el.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => { useEffect(() => {
if (agentId) { if (agentId) {
updateData(); setShowCaseList([]);
updateData(1);
setPageNo(1);
} }
}, [agentId]); }, [agentId]);
const showCaseClass = classNames(styles.showCase, { [styles.mobile]: isMobile });
return ( return (
<Spin spinning={loading} size="large"> <Spin spinning={loading} size="large">
<div className={styles.showCase}> <div className={showCaseClass} style={{ height }} ref={showcaseRef}>
{Object.keys(showCaseMap || {}).map(key => { <div className={styles.showCaseContent}>
const showCaseItem = showCaseMap?.[key] || []; {showCaseList.map(showCaseItem => {
return ( return (
<div key={key} className={styles.showCaseItem}> <div key={showCaseItem.caseId} className={styles.showCaseItem}>
{showCaseItem {showCaseItem.msgList
.filter((chatItem: HistoryMsgItemType) => !!chatItem.queryResult) .filter((chatItem: HistoryMsgItemType) => !!chatItem.queryResult)
.slice(0, 10) .slice(0, 1)
.map((chatItem: HistoryMsgItemType) => { .map((chatItem: HistoryMsgItemType) => {
return ( return (
<div className={styles.showCaseChatItem} key={chatItem.questionId}> <div className={styles.showCaseChatItem} key={chatItem.questionId}>
<Text position="right" data={chatItem.queryText} anonymousUser /> <Text position="right" data={chatItem.queryText} anonymousUser />
<ChatItem <ChatItem
msg={chatItem.queryText} msg={chatItem.queryText}
parseInfos={chatItem.parseInfos}
msgData={chatItem.queryResult} msgData={chatItem.queryResult}
conversationId={chatItem.chatId} conversationId={chatItem.chatId}
agentId={agentId} agentId={agentId}
integrateSystem="showcase" integrateSystem="showcase"
score={chatItem.score}
onSendMsg={onSendMsg} onSendMsg={onSendMsg}
/> />
</div> </div>
@@ -58,6 +112,7 @@ const ShowCase: React.FC<Props> = ({ agentId, onSendMsg }) => {
); );
})} })}
</div> </div>
</div>
</Spin> </Spin>
); );
}; };

View File

@@ -1,9 +1,21 @@
.showCase { .showCase {
position: relative;
height: 100%;
padding: 0 20px;
overflow-y: auto;
padding-bottom: 2px;
.showCaseContent {
column-count: 2; column-count: 2;
column-gap: 20px; column-gap: 20px;
height: 100%;
min-height: 400px; .showcaseLoading {
overflow-y: auto; display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 400px;
}
.showCaseItem { .showCaseItem {
display: flex; display: flex;
@@ -11,7 +23,6 @@
row-gap: 12px; row-gap: 12px;
padding: 12px; padding: 12px;
margin-bottom: 20px; margin-bottom: 20px;
max-height: 800px;
overflow-y: auto; overflow-y: auto;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12);
@@ -25,3 +36,11 @@
} }
} }
} }
&.mobile {
padding: 0 4px;
.showCaseContent {
column-count: 1;
}
}
}

View File

@@ -2,6 +2,11 @@ import { HistoryMsgItemType } from "../common/type";
export type ShowCaseMapType = Record<number, HistoryMsgItemType[]>; export type ShowCaseMapType = Record<number, HistoryMsgItemType[]>;
export type ShowCaseItemType = {
caseId: string;
msgList: HistoryMsgItemType[];
}
export type ShowCaseType = { export type ShowCaseType = {
showCaseMap: ShowCaseMapType, showCaseMap: ShowCaseMapType,
current: number, current: number,

View File

@@ -136,7 +136,8 @@ export type MsgDataType = {
queryState: string; queryState: string;
queryText: string; queryText: string;
response: PluginResonseType; response: PluginResonseType;
parseOptions?: ChatContextType[]; parseInfos?: ChatContextType[];
queryTimeCost?: number;
}; };
export enum ParseStateEnum { export enum ParseStateEnum {
@@ -211,6 +212,7 @@ export type HistoryMsgItemType = {
questionId: number; questionId: number;
queryText: string; queryText: string;
parseInfos: ChatContextType[]; parseInfos: ChatContextType[];
parseTimeCost: ParseTimeCostType;
queryResult: MsgDataType; queryResult: MsgDataType;
chatId: number; chatId: number;
createTime: string; createTime: string;
@@ -242,3 +244,8 @@ export type SimilarQuestionType = {
// parseId: number; // parseId: number;
queryText: string; queryText: string;
} }
export type ParseTimeCostType = {
parseTime: number;
sqlTime: number;
}

View File

@@ -1,5 +1,5 @@
import { Button, Spin } from 'antd'; import { Spin } from 'antd';
import { CheckCircleFilled, ReloadOutlined } from '@ant-design/icons'; import { CheckCircleFilled } 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';
@@ -17,7 +17,7 @@ type Props = {
renderCustomExecuteNode?: boolean; renderCustomExecuteNode?: boolean;
data?: MsgDataType; data?: MsgDataType;
triggerResize?: boolean; triggerResize?: boolean;
onRefresh: () => void; isDeveloper?: boolean;
}; };
const ExecuteItem: React.FC<Props> = ({ const ExecuteItem: React.FC<Props> = ({
@@ -30,7 +30,7 @@ const ExecuteItem: React.FC<Props> = ({
renderCustomExecuteNode, renderCustomExecuteNode,
data, data,
triggerResize, triggerResize,
onRefresh, isDeveloper,
}) => { }) => {
const prefixCls = `${PREFIX_CLS}-item`; const prefixCls = `${PREFIX_CLS}-item`;
@@ -49,19 +49,20 @@ 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(<>{reloadNode}</>, executeTip); return getNodeTip(
<>
{data?.queryTimeCost && isDeveloper && (
<span className={`${prefixCls}-title-tip`}>(: {data.queryTimeCost}ms)</span>
)}
</>,
executeTip
);
} }
if (!data) { if (!data) {
@@ -73,8 +74,10 @@ 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 className={`${prefixCls}-step-title`}>
{reloadNode} {data?.queryTimeCost && isDeveloper && (
<span className={`${prefixCls}-title-tip`}>(: {data.queryTimeCost}ms)</span>
)}
</div> </div>
</div> </div>
<div className={`${prefixCls}-content-container`}> <div className={`${prefixCls}-content-container`}>

View File

@@ -1,15 +1,17 @@
import { Select, Spin, InputNumber } from 'antd'; import { Select, Spin, InputNumber, DatePicker } from 'antd';
import { PREFIX_CLS } from '../../common/constants'; import { PREFIX_CLS } from '../../common/constants';
import { ChatContextType, 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, isArray } from 'lodash'; import { debounce, isArray } from 'lodash';
import SwicthEntity from './SwitchEntity'; import SwicthEntity from './SwitchEntity';
import moment from 'moment';
type Props = { type Props = {
modelId: number; modelId: number;
filters: FilterItemType[]; filters: FilterItemType[];
filter: FilterItemType; filter: FilterItemType;
index: number;
chatContext: ChatContextType; chatContext: ChatContextType;
agentId?: number; agentId?: number;
entityAlias?: string; entityAlias?: string;
@@ -22,6 +24,7 @@ const FilterItem: React.FC<Props> = ({
modelId, modelId,
filters, filters,
filter, filter,
index,
chatContext, chatContext,
agentId, agentId,
entityAlias, entityAlias,
@@ -44,7 +47,7 @@ const FilterItem: React.FC<Props> = ({
'' ''
); );
setOptions( setOptions(
data?.resultList.map((item: any) => ({ data?.resultList?.map((item: any) => ({
label: item[filter.bizName], label: item[filter.bizName],
value: item[filter.bizName], value: item[filter.bizName],
})) || [] })) || []
@@ -87,8 +90,8 @@ const FilterItem: React.FC<Props> = ({
}, [queryDimensionValues]); }, [queryDimensionValues]);
const onOperatorChange = (value: string) => { const onOperatorChange = (value: string) => {
const newFilters = filters.map(item => { const newFilters = filters.map((item, indexValue) => {
if (item.bizName === filter.bizName) { if (item.bizName === filter.bizName && index === indexValue) {
item.operator = value; item.operator = value;
} }
return item; return item;
@@ -97,8 +100,8 @@ const FilterItem: React.FC<Props> = ({
}; };
const onChange = (value: string | string[] | number | null) => { const onChange = (value: string | string[] | number | null) => {
const newFilters = filters.map(item => { const newFilters = filters.map((item, indexValue) => {
if (item.bizName === filter.bizName) { if (item.bizName === filter.bizName && index === indexValue) {
item.value = item.value =
typeof filter.value === 'number' || filter.value === null typeof filter.value === 'number' || filter.value === null
? value ? value
@@ -123,28 +126,46 @@ const FilterItem: React.FC<Props> = ({
onFiltersChange(newFilters); onFiltersChange(newFilters);
}; };
const onDateChange = (_: any, date: string) => {
console.log('onDateChange', date);
const newFilters = filters.map((item, indexValue) => {
if (item.bizName === filter.bizName && index === indexValue) {
item.value = date;
}
return item;
});
onFiltersChange(newFilters);
};
return ( return (
<span className={prefixCls}> <span className={prefixCls}>
<span className={`${prefixCls}-filter-name`}>{filter.name}</span> <span className={`${prefixCls}-filter-name`}>{filter.name}</span>
{(typeof filter.value === 'number' || filter.value === null) && {(typeof filter.value === 'number' ||
!filter.bizName?.includes('_id') ? ( filter.value === null ||
<> (filter.operator && !['IN', '=', 'LIKE'].includes(filter.operator))) &&
!filter.bizName?.includes('_id') && (
<Select <Select
options={[ options={[
{ label: '大于等于', value: '>=' },
{ label: '大于', value: '>' }, { label: '大于', value: '>' },
{ label: '等于', value: '=' }, { label: '等于', value: '=' },
{ label: '小于等于', value: '<=' },
{ label: '小于', value: '<' }, { label: '小于', value: '<' },
]} ]}
className={`${prefixCls}-operator-control`} className={`${prefixCls}-operator-control`}
value={filter.operator} value={filter.operator}
onChange={onOperatorChange} onChange={onOperatorChange}
/> />
)}
{(typeof filter.value === 'number' || filter.value === null) &&
!filter.bizName?.includes('_id') ? (
<InputNumber <InputNumber
className={`${prefixCls}-input-number-control`} className={`${prefixCls}-input-number-control`}
value={filter.value} value={filter.value}
onChange={onChange} onChange={onChange}
/> />
</> ) : typeof filter.value === 'string' && moment(filter.value, 'YYYY-MM-DD').isValid() ? (
<DatePicker value={moment(filter.value)} onChange={onDateChange} allowClear={false} />
) : (typeof filter.value === 'string' || isArray(filter.value)) && ) : (typeof filter.value === 'string' || isArray(filter.value)) &&
!filter.bizName?.includes('_id') ? ( !filter.bizName?.includes('_id') ? (
<Select <Select
@@ -173,7 +194,9 @@ const FilterItem: React.FC<Props> = ({
</span> </span>
</> </>
) : ( ) : (
<span className={`${prefixCls}-filter-value`}>{filter.value}</span> <span className={`${prefixCls}-filter-value`}>
{typeof filter.value !== 'object' ? filter.value : ''}
</span>
)} )}
</span> </span>
); );

View File

@@ -1,8 +1,8 @@
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, DateInfoType, EntityInfoType, FilterItemType } from '../../common/type'; import { ChatContextType, DateInfoType, EntityInfoType, FilterItemType } from '../../common/type';
import { DatePicker } from 'antd'; import { Button, DatePicker } from 'antd';
import { CheckCircleFilled } from '@ant-design/icons'; import { CheckCircleFilled, ReloadOutlined } from '@ant-design/icons';
import Loading from './Loading'; import Loading from './Loading';
import FilterItem from './FilterItem'; import FilterItem from './FilterItem';
import moment from 'moment'; import moment from 'moment';
@@ -21,10 +21,13 @@ type Props = {
dateInfo: DateInfoType; dateInfo: DateInfoType;
entityInfo: EntityInfoType; entityInfo: EntityInfoType;
integrateSystem?: string; integrateSystem?: string;
parseTimeCost?: number;
isDeveloper?: boolean;
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; onDateInfoChange: (dateRange: any) => void;
onRefresh: () => void;
}; };
const MAX_OPTION_VALUES_COUNT = 2; const MAX_OPTION_VALUES_COUNT = 2;
@@ -39,10 +42,13 @@ const ParseTip: React.FC<Props> = ({
dateInfo, dateInfo,
entityInfo, entityInfo,
integrateSystem, integrateSystem,
parseTimeCost,
isDeveloper,
onSelectParseInfo, onSelectParseInfo,
onSwitchEntity, onSwitchEntity,
onFiltersChange, onFiltersChange,
onDateInfoChange, onDateInfoChange,
onRefresh,
}) => { }) => {
const prefixCls = `${PREFIX_CLS}-item`; const prefixCls = `${PREFIX_CLS}-item`;
@@ -66,7 +72,15 @@ const ParseTip: React.FC<Props> = ({
} }
if (parseTip) { if (parseTip) {
return getNode('意图解析失败', parseTip); return getNode(
<>
{parseTimeCost && isDeveloper && (
<span className={`${prefixCls}-title-tip`}>(: {parseTimeCost}ms)</span>
)}
</>,
parseTip
);
} }
if (parseInfoOptions.length === 0) { if (parseInfoOptions.length === 0) {
@@ -218,18 +232,19 @@ const ParseTip: React.FC<Props> = ({
/> />
)} )}
</div> </div>
{filters?.map((filter: any) => ( {filters?.map((filter: any, index: number) => (
<FilterItem <FilterItem
modelId={modelId!} modelId={modelId!}
filters={dimensionFilters} filters={dimensionFilters}
filter={filter} filter={filter}
index={index}
chatContext={currentParseInfo!} chatContext={currentParseInfo!}
entityAlias={entityAlias} entityAlias={entityAlias}
agentId={agentId} agentId={agentId}
integrateSystem={integrateSystem} integrateSystem={integrateSystem}
onFiltersChange={onFiltersChange} onFiltersChange={onFiltersChange}
onSwitchEntity={onSwitchEntity} onSwitchEntity={onSwitchEntity}
key={filter.name} key={`${filter.name}_${index}`}
/> />
))} ))}
</div> </div>
@@ -238,23 +253,39 @@ const ParseTip: React.FC<Props> = ({
const getFiltersNode = () => { const getFiltersNode = () => {
return ( return (
<>
<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={`${prefixCls}-tip-item-content`}>{getFilterContent(dimensionFilters)}</div> <div className={`${prefixCls}-tip-item-content`}>
{getFilterContent(dimensionFilters)}
</div> </div>
</div>
<Button className={`${prefixCls}-reload`} size="small" onClick={onRefresh}>
<ReloadOutlined />
</Button>
</>
); );
}; };
const { type: agentType } = properties || {};
const tipNode = ( const tipNode = (
<div className={`${prefixCls}-tip`}> <div className={`${prefixCls}-tip`}>
{getTipNode()} {getTipNode()}
{getFiltersNode()} {!(!!agentType && queryMode !== 'LLM_S2QL') && getFiltersNode()}
</div> </div>
); );
return getNode( return getNode(
<div className={`${prefixCls}-title-bar`}> <div className={`${prefixCls}-title-bar`}>
<div>{parseInfoOptions?.length > 1 ? '' : ''}</div> <div>
{parseTimeCost && isDeveloper && (
<span className={`${prefixCls}-title-tip`}>(: {parseTimeCost}ms)</span>
)}
{parseInfoOptions?.length > 1 ? '' : ''}
</div>
{parseInfoOptions?.length > 1 && ( {parseInfoOptions?.length > 1 && (
<div className={`${prefixCls}-content-options`}> <div className={`${prefixCls}-content-options`}>
{parseInfoOptions.map((parseInfo, index) => ( {parseInfoOptions.map((parseInfo, index) => (

View File

@@ -11,9 +11,10 @@ import { SqlInfoType } from '../../common/type';
type Props = { type Props = {
integrateSystem?: string; integrateSystem?: string;
sqlInfo: SqlInfoType; sqlInfo: SqlInfoType;
sqlTimeCost?: number;
}; };
const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => { const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo, sqlTimeCost }) => {
const [sqlType, setSqlType] = useState(''); const [sqlType, setSqlType] = useState('');
const tipPrefixCls = `${PREFIX_CLS}-item`; const tipPrefixCls = `${PREFIX_CLS}-item`;
@@ -36,7 +37,11 @@ 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生成
{sqlTimeCost && (
<span className={`${tipPrefixCls}-title-tip`}>(: {sqlTimeCost}ms)</span>
)}
{sqlType && ( {sqlType && (
<span className={`${prefixCls}-toggle-expand-btn`} onClick={onCollapse}> <span className={`${prefixCls}-toggle-expand-btn`} onClick={onCollapse}>
<UpOutlined /> <UpOutlined />
@@ -53,7 +58,7 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => {
setSqlType(sqlType === 's2QL' ? '' : 's2QL'); setSqlType(sqlType === 's2QL' ? '' : 's2QL');
}} }}
> >
S2QL S2QL
</div> </div>
)} )}
{sqlInfo.logicSql && ( {sqlInfo.logicSql && (
@@ -65,7 +70,7 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => {
setSqlType(sqlType === 'logicSql' ? '' : 'logicSql'); setSqlType(sqlType === 'logicSql' ? '' : 'logicSql');
}} }}
> >
SQL S2QL
</div> </div>
)} )}
{sqlInfo.querySql && ( {sqlInfo.querySql && (
@@ -77,7 +82,7 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => {
setSqlType(sqlType === 'querySql' ? '' : 'querySql'); setSqlType(sqlType === 'querySql' ? '' : 'querySql');
}} }}
> >
SQL SQL
</div> </div>
)} )}
</div> </div>
@@ -91,7 +96,7 @@ const SqlItem: React.FC<Props> = ({ integrateSystem, sqlInfo }) => {
: '' : ''
}`} }`}
> >
{sqlType && ( {sqlType && sqlInfo[sqlType] && (
<> <>
<SyntaxHighlighter <SyntaxHighlighter
className={`${prefixCls}-code`} className={`${prefixCls}-code`}

View File

@@ -5,6 +5,7 @@ import {
FilterItemType, FilterItemType,
MsgDataType, MsgDataType,
ParseStateEnum, ParseStateEnum,
ParseTimeCostType,
SimilarQuestionType, SimilarQuestionType,
} from '../../common/type'; } from '../../common/type';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -27,8 +28,8 @@ type Props = {
agentId?: number; agentId?: number;
score?: number; score?: number;
filter?: any[]; filter?: any[];
isLastMessage?: boolean;
parseInfos?: ChatContextType[]; parseInfos?: ChatContextType[];
parseTimeCostValue?: ParseTimeCostType;
msgData?: MsgDataType; msgData?: MsgDataType;
triggerResize?: boolean; triggerResize?: boolean;
isDeveloper?: boolean; isDeveloper?: boolean;
@@ -47,9 +48,9 @@ const ChatItem: React.FC<Props> = ({
agentId, agentId,
score, score,
filter, filter,
isLastMessage,
triggerResize, triggerResize,
parseInfos, parseInfos,
parseTimeCostValue,
msgData, msgData,
isDeveloper, isDeveloper,
integrateSystem, integrateSystem,
@@ -61,6 +62,7 @@ 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 [parseTimeCost, setParseTimeCost] = useState<ParseTimeCostType>();
const [parseInfo, setParseInfo] = useState<ChatContextType>(); const [parseInfo, setParseInfo] = useState<ChatContextType>();
const [parseInfoOptions, setParseInfoOptions] = useState<ChatContextType[]>([]); const [parseInfoOptions, setParseInfoOptions] = useState<ChatContextType[]>([]);
const [parseTip, setParseTip] = useState(''); const [parseTip, setParseTip] = useState('');
@@ -97,24 +99,50 @@ const ChatItem: React.FC<Props> = ({
return true; return true;
}; };
const onExecute = async (parseInfoValue: ChatContextType) => { const onExecute = async (
parseInfoValue: ChatContextType,
parseInfos?: ChatContextType[],
isSwitchParseInfo?: boolean
) => {
setExecuteMode(true); setExecuteMode(true);
if (isSwitchParseInfo) {
setEntitySwitchLoading(true);
} else {
setExecuteLoading(true); setExecuteLoading(true);
}
try { try {
const res: any = await chatExecute(msg, conversationId!, parseInfoValue); const res: any = await chatExecute(msg, conversationId!, parseInfoValue);
setExecuteLoading(false);
const valid = updateData(res); const valid = updateData(res);
onMsgDataLoaded?.( onMsgDataLoaded?.(
{ {
...res.data, ...res.data,
chatContext: parseInfoValue, parseInfos,
queryId: parseInfoValue.queryId,
}, },
valid valid
); );
} catch (e) { } catch (e) {
setExecuteLoading(false);
setExecuteTip(SEARCH_EXCEPTION_TIP); setExecuteTip(SEARCH_EXCEPTION_TIP);
} }
if (isSwitchParseInfo) {
setEntitySwitchLoading(false);
} else {
setExecuteLoading(false);
}
};
const updateDimensionFitlers = (filters: FilterItemType[]) => {
setDimensionFilters(
filters.sort((a, b) => {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
})
);
}; };
const sendMsg = async () => { const sendMsg = async () => {
@@ -122,7 +150,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, candidateParses, queryId } = data || {}; const { state, selectedParses, candidateParses, queryId, parseTimeCost } = data || {};
if ( if (
code !== 200 || code !== 200 ||
state === ParseStateEnum.FAILED || state === ParseStateEnum.FAILED ||
@@ -143,10 +171,11 @@ const ChatItem: React.FC<Props> = ({
setParseInfoOptions(parseInfos || []); setParseInfoOptions(parseInfos || []);
const parseInfoValue = parseInfos[0]; const parseInfoValue = parseInfos[0];
setParseInfo(parseInfoValue); setParseInfo(parseInfoValue);
setParseTimeCost(parseTimeCost);
setEntityInfo(parseInfoValue.entityInfo || {}); setEntityInfo(parseInfoValue.entityInfo || {});
setDimensionFilters(parseInfoValue?.dimensionFilters || []); updateDimensionFitlers(parseInfoValue?.dimensionFilters || []);
setDateInfo(parseInfoValue?.dateInfo); setDateInfo(parseInfoValue?.dateInfo);
onExecute(parseInfoValue); onExecute(parseInfoValue, parseInfos);
}; };
useEffect(() => { useEffect(() => {
@@ -161,7 +190,8 @@ const ChatItem: React.FC<Props> = ({
const parseInfoValue = parseInfoOptionsValue[0]; const parseInfoValue = parseInfoOptionsValue[0];
setParseInfoOptions(parseInfoOptionsValue); setParseInfoOptions(parseInfoOptionsValue);
setParseInfo(parseInfoValue); setParseInfo(parseInfoValue);
setDimensionFilters(parseInfoValue.dimensionFilters || []); setParseTimeCost(parseTimeCostValue);
updateDimensionFitlers(parseInfoValue.dimensionFilters || []);
setDateInfo(parseInfoValue.dateInfo); setDateInfo(parseInfoValue.dateInfo);
setExecuteMode(true); setExecuteMode(true);
updateData({ code: 200, data: msgData, msg: 'success' }); updateData({ code: 200, data: msgData, msg: 'success' });
@@ -179,7 +209,7 @@ const ChatItem: React.FC<Props> = ({
const chatContextValue = { ...(chatContext || {}), queryId: parseInfo?.queryId }; const chatContextValue = { ...(chatContext || {}), queryId: parseInfo?.queryId };
setParseInfo(chatContextValue); setParseInfo(chatContextValue);
setEntityInfo(entityInfo); setEntityInfo(entityInfo);
setDimensionFilters(chatContextValue?.dimensionFilters || []); updateDimensionFitlers(chatContextValue?.dimensionFilters || []);
setDateInfo(chatContextValue?.dateInfo); setDateInfo(chatContextValue?.dateInfo);
}; };
@@ -213,7 +243,12 @@ const ChatItem: React.FC<Props> = ({
if (res.code === 200) { if (res.code === 200) {
const resChatContext = res.data?.chatContext; const resChatContext = res.data?.chatContext;
const contextValue = { ...(resChatContext || chatContextValue), queryId }; const contextValue = { ...(resChatContext || chatContextValue), queryId };
const dataValue = { ...res.data, chatContext: contextValue }; const dataValue = {
...res.data,
chatContext: contextValue,
parseInfos: parseInfoOptions,
queryId,
};
onMsgDataLoaded?.(dataValue, true, true); onMsgDataLoaded?.(dataValue, true, true);
setData(dataValue); setData(dataValue);
setParseInfo(contextValue); setParseInfo(contextValue);
@@ -227,13 +262,14 @@ const ChatItem: React.FC<Props> = ({
const onSelectParseInfo = async (parseInfoValue: ChatContextType) => { const onSelectParseInfo = async (parseInfoValue: ChatContextType) => {
setParseInfo(parseInfoValue); setParseInfo(parseInfoValue);
setDimensionFilters(parseInfoValue.dimensionFilters || []); updateDimensionFitlers(parseInfoValue.dimensionFilters || []);
setDateInfo(parseInfoValue.dateInfo); setDateInfo(parseInfoValue.dateInfo);
if (parseInfoValue.entityInfo) { if (parseInfoValue.entityInfo) {
setEntityInfo(parseInfoValue.entityInfo); setEntityInfo(parseInfoValue.entityInfo);
} else { } else {
getEntityInfo(parseInfoValue); getEntityInfo(parseInfoValue);
} }
onExecute(parseInfoValue, parseInfoOptions, true);
}; };
const onSelectQuestion = (question: SimilarQuestionType) => { const onSelectQuestion = (question: SimilarQuestionType) => {
@@ -261,15 +297,22 @@ const ChatItem: React.FC<Props> = ({
dateInfo={dateInfo} dateInfo={dateInfo}
entityInfo={entityInfo} entityInfo={entityInfo}
integrateSystem={integrateSystem} integrateSystem={integrateSystem}
parseTimeCost={parseTimeCost?.parseTime}
isDeveloper={isDeveloper}
onSelectParseInfo={onSelectParseInfo} onSelectParseInfo={onSelectParseInfo}
onSwitchEntity={onSwitchEntity} onSwitchEntity={onSwitchEntity}
onFiltersChange={onFiltersChange} onFiltersChange={onFiltersChange}
onDateInfoChange={onDateInfoChange} onDateInfoChange={onDateInfoChange}
onRefresh={onRefresh}
/> />
{executeMode && ( {executeMode && (
<> <>
{!isMobile && parseInfo?.sqlInfo && isDeveloper && integrateSystem !== 'c2' && ( {!isMobile && parseInfo?.sqlInfo && isDeveloper && integrateSystem !== 'c2' && (
<SqlItem integrateSystem={integrateSystem} sqlInfo={parseInfo.sqlInfo} /> <SqlItem
integrateSystem={integrateSystem}
sqlInfo={parseInfo.sqlInfo}
sqlTimeCost={parseTimeCost?.sqlTime}
/>
)} )}
<ExecuteItem <ExecuteItem
queryId={parseInfo?.queryId} queryId={parseInfo?.queryId}
@@ -280,8 +323,8 @@ const ChatItem: React.FC<Props> = ({
data={data} data={data}
triggerResize={triggerResize} triggerResize={triggerResize}
executeItemNode={executeItemNode} executeItemNode={executeItemNode}
isDeveloper={isDeveloper}
renderCustomExecuteNode={renderCustomExecuteNode} renderCustomExecuteNode={renderCustomExecuteNode}
onRefresh={onRefresh}
/> />
</> </>
)} )}
@@ -294,9 +337,7 @@ const ChatItem: React.FC<Props> = ({
/> />
)} )}
</div> </div>
{(parseTip !== '' || (executeMode && !executeLoading)) && {(parseTip !== '' || (executeMode && !executeLoading)) && integrateSystem !== 'c2' && (
integrateSystem !== 'c2' &&
integrateSystem !== 'showcase' && (
<Tools queryId={parseInfo?.queryId || 0} scoreValue={score} /> <Tools queryId={parseInfo?.queryId || 0} scoreValue={score} />
)} )}
</div> </div>

View File

@@ -133,6 +133,12 @@
column-gap: 10px; column-gap: 10px;
} }
&-title-tip {
margin-left: 2px;
color: var(--text-color-third);
font-weight: normal;
}
&-step-title { &-step-title {
font-weight: 500; font-weight: 500;
color: var(--text-color); color: var(--text-color);
@@ -140,6 +146,7 @@
&-reload { &-reload {
margin-left: 2px; margin-left: 2px;
width: fit-content;
font-weight: normal; font-weight: normal;
color: var(--text-color-secondary); color: var(--text-color-secondary);
font-size: 13px !important; font-size: 13px !important;

View File

@@ -36,7 +36,9 @@ const BarChart: React.FC<Props> = ({ data, triggerResize, loading, onApplyAuth }
const data = (queryResults || []).sort( const data = (queryResults || []).sort(
(a: any, b: any) => b[metricColumnName] - a[metricColumnName] (a: any, b: any) => b[metricColumnName] - a[metricColumnName]
); );
const xData = data.map(item => item[categoryColumnName]); const xData = data.map(item =>
item[categoryColumnName] !== undefined ? item[categoryColumnName] : '未知'
);
instanceObj.setOption({ instanceObj.setOption({
xAxis: { xAxis: {
type: 'category', type: 'category',

View File

@@ -52,7 +52,9 @@ const MetricCard: React.FC<Props> = ({ data, loading, onApplyAuth }) => {
) : ( ) : (
<div style={{ display: 'flex', alignItems: 'flex-end' }}> <div style={{ display: 'flex', alignItems: 'flex-end' }}>
<div className={`${prefixCls}-indicator-value`}> <div className={`${prefixCls}-indicator-value`}>
{dataFormatType === 'percent' || dataFormatType === 'decimal' {typeof value === 'string' && isNaN(+value)
? value
: dataFormatType === 'percent' || dataFormatType === 'decimal'
? `${formatByDecimalPlaces( ? `${formatByDecimalPlaces(
dataFormat?.needMultiply100 ? +value * 100 : value, dataFormat?.needMultiply100 ? +value * 100 : value,
dataFormat?.decimalPlaces || 2 dataFormat?.decimalPlaces || 2

View File

@@ -1,4 +1,4 @@
import { formatByDecimalPlaces, getFormattedValue } from '../../../utils/utils'; import { formatByDecimalPlaces, getFormattedValue, isMobile } from '../../../utils/utils';
import { Table as AntTable } from 'antd'; import { Table as AntTable } from 'antd';
import { MsgDataType } from '../../../common/type'; import { MsgDataType } from '../../../common/type';
import { CLS_PREFIX } from '../../../common/constants'; import { CLS_PREFIX } from '../../../common/constants';
@@ -70,7 +70,13 @@ const Table: React.FC<Props> = ({ data, size, onApplyAuth }) => {
<div className={prefixCls}> <div className={prefixCls}>
<AntTable <AntTable
pagination={ pagination={
queryResults.length <= 10 ? false : { defaultPageSize: 10, position: ['bottomCenter'] } queryResults.length <= 10
? false
: {
defaultPageSize: 10,
position: ['bottomCenter'],
size: isMobile ? 'small' : 'default',
}
} }
columns={tableColumns} columns={tableColumns}
dataSource={dataSource} dataSource={dataSource}

View File

@@ -10,6 +10,7 @@ import { PREFIX_CLS } from '../../common/constants';
import Text from './Text'; import Text from './Text';
import DrillDownDimensions from '../DrillDownDimensions'; import DrillDownDimensions from '../DrillDownDimensions';
import MetricOptions from '../MetricOptions'; import MetricOptions from '../MetricOptions';
import { isMobile } from '../../utils/utils';
type Props = { type Props = {
queryId?: number; queryId?: number;
@@ -115,7 +116,11 @@ const ChatMsg: React.FC<Props> = ({ queryId, data, chartIndex, triggerResize })
/> />
); );
} }
if (categoryField?.length > 0 && metricFields?.length > 0) { if (
categoryField?.length > 0 &&
metricFields?.length > 0 &&
(isMobile ? dataSource?.length <= 20 : dataSource?.length <= 50)
) {
return ( return (
<Bar <Bar
data={{ ...data, queryColumns: columns, queryResults: dataSource }} data={{ ...data, queryColumns: columns, queryResults: dataSource }}

View File

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

View File

@@ -60,7 +60,6 @@ const Chat = () => {
agentId={5} agentId={5}
conversationId={112211121} conversationId={112211121}
onMsgDataLoaded={onMsgDataLoaded} onMsgDataLoaded={onMsgDataLoaded}
isLastMessage
triggerResize={triggerResize} triggerResize={triggerResize}
integrateSystem="wiki" integrateSystem="wiki"
isDeveloper isDeveloper

21
webapp/pnpm-lock.yaml generated
View File

@@ -319,8 +319,8 @@ importers:
specifier: ^4.8.14 specifier: ^4.8.14
version: 4.8.22 version: 4.8.22
'@antv/g6-core': '@antv/g6-core':
specifier: ^0.8.21 specifier: ^0.8.23
version: 0.8.22 version: 0.8.23
'@antv/layout': '@antv/layout':
specifier: ^0.3.20 specifier: ^0.3.20
version: 0.3.23(dagre@0.8.5) version: 0.3.23(dagre@0.8.5)
@@ -1514,6 +1514,21 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: false dev: false
/@antv/g6-core@0.8.23:
resolution: {integrity: sha512-JWdnba5Bx4/hLhbIQeyvdgh68SDYZisveukuBifxLKODCNJNKTopmWf1w6tU+RxAT2k5ByXkTGWQE1IkIL8O+Q==}
dependencies:
'@antv/algorithm': 0.1.26
'@antv/dom-util': 2.0.4
'@antv/event-emitter': 0.1.3
'@antv/g-base': 0.5.15
'@antv/g-math': 0.1.9
'@antv/matrix-util': 3.1.0-beta.3
'@antv/path-util': 2.0.15
'@antv/util': 2.0.17
ml-matrix: 6.10.4
tslib: 2.6.2
dev: false
/@antv/g6-element@0.8.22: /@antv/g6-element@0.8.22:
resolution: {integrity: sha512-rTJgaFDeaiHEF5d+hdJEGbh+Z272bi+Zzu5aTDWCtFFgxtosdeOQGWdyhIEeLCstRvlP3RaaztBIsSZm5BWFsA==} resolution: {integrity: sha512-rTJgaFDeaiHEF5d+hdJEGbh+Z272bi+Zzu5aTDWCtFFgxtosdeOQGWdyhIEeLCstRvlP3RaaztBIsSZm5BWFsA==}
dependencies: dependencies:
@@ -1533,7 +1548,7 @@ packages:
'@antv/g-canvas': 0.5.14 '@antv/g-canvas': 0.5.14
'@antv/g-math': 0.1.9 '@antv/g-math': 0.1.9
'@antv/g-svg': 0.5.7 '@antv/g-svg': 0.5.7
'@antv/g6-core': 0.8.22 '@antv/g6-core': 0.8.23
'@antv/g6-element': 0.8.22 '@antv/g6-element': 0.8.22
'@antv/g6-plugin': 0.8.22 '@antv/g6-plugin': 0.8.22
'@antv/hierarchy': 0.6.11 '@antv/hierarchy': 0.6.11