mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-11 12:07:42 +00:00
[feature](weaapp) add agent
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "supersonic-chat-sdk",
|
"name": "supersonic-chat-sdk",
|
||||||
"version": "0.3.0",
|
"version": "0.4.32",
|
||||||
"main": "dist/index.es.js",
|
"main": "dist/index.es.js",
|
||||||
"module": "dist/index.es.js",
|
"module": "dist/index.es.js",
|
||||||
"unpkg": "dist/index.umd.js",
|
"unpkg": "dist/index.umd.js",
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ export const THEME_COLOR_LIST = [
|
|||||||
'#5CA9E6',
|
'#5CA9E6',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PARSE_ERROR_TIP = '小Q不太懂您说什么呐,回去一定补充知识';
|
export const PARSE_ERROR_TIP = '智能助理不太懂您说什么呐,回去一定补充知识';
|
||||||
|
|
||||||
export const SEARCH_EXCEPTION_TIP = '查询出错啦,智能小Q还不够聪明,请您换个表达再试试';
|
export const SEARCH_EXCEPTION_TIP = '查询出错啦,智能助理还不够聪明,请您换个表达再试试';
|
||||||
|
|
||||||
export const MSG_VALID_TIP = {
|
export const MSG_VALID_TIP = {
|
||||||
[MsgValidTypeEnum.SEARCH_EXCEPTION]: '数据查询异常',
|
[MsgValidTypeEnum.SEARCH_EXCEPTION]: '数据查询异常',
|
||||||
[MsgValidTypeEnum.INVALID]: '小Q不太懂您说什么呐,回去一定补充知识',
|
[MsgValidTypeEnum.INVALID]: '智能助理不太懂您说什么呐,回去一定补充知识',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PREFIX_CLS = 'ss-chat';
|
export const PREFIX_CLS = 'ss-chat';
|
||||||
|
|||||||
@@ -51,10 +51,21 @@ export type FilterItemType = {
|
|||||||
value: string[];
|
value: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ModelType = {
|
||||||
|
alias: string;
|
||||||
|
bizName: string;
|
||||||
|
id: number;
|
||||||
|
model: number;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
useCnt: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type ChatContextType = {
|
export type ChatContextType = {
|
||||||
aggType: string;
|
aggType: string;
|
||||||
modelId: number;
|
modelId: number;
|
||||||
modelName: string;
|
modelName: string;
|
||||||
|
model: ModelType;
|
||||||
dateInfo: DateInfoType;
|
dateInfo: DateInfoType;
|
||||||
dimensions: FieldType[];
|
dimensions: FieldType[];
|
||||||
metrics: FieldType[];
|
metrics: FieldType[];
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Spin } from 'antd';
|
||||||
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';
|
||||||
@@ -8,6 +9,7 @@ import Typing from './Typing';
|
|||||||
type Props = {
|
type Props = {
|
||||||
question: string;
|
question: string;
|
||||||
executeLoading: boolean;
|
executeLoading: boolean;
|
||||||
|
entitySwitchLoading: boolean;
|
||||||
chartIndex: number;
|
chartIndex: number;
|
||||||
executeTip?: string;
|
executeTip?: string;
|
||||||
data?: MsgDataType;
|
data?: MsgDataType;
|
||||||
@@ -21,6 +23,7 @@ type Props = {
|
|||||||
const ExecuteItem: React.FC<Props> = ({
|
const ExecuteItem: React.FC<Props> = ({
|
||||||
question,
|
question,
|
||||||
executeLoading,
|
executeLoading,
|
||||||
|
entitySwitchLoading,
|
||||||
chartIndex,
|
chartIndex,
|
||||||
executeTip,
|
executeTip,
|
||||||
data,
|
data,
|
||||||
@@ -50,6 +53,7 @@ const ExecuteItem: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${prefixCls}-msg-content`}>
|
<div className={`${prefixCls}-msg-content`}>
|
||||||
|
<Spin spinning={entitySwitchLoading}>
|
||||||
<ChatMsg
|
<ChatMsg
|
||||||
question={question}
|
question={question}
|
||||||
data={data}
|
data={data}
|
||||||
@@ -57,6 +61,7 @@ const ExecuteItem: React.FC<Props> = ({
|
|||||||
isMobileMode={isMobileMode}
|
isMobileMode={isMobileMode}
|
||||||
triggerResize={triggerResize}
|
triggerResize={triggerResize}
|
||||||
/>
|
/>
|
||||||
|
</Spin>
|
||||||
{!isMetricCard && (
|
{!isMetricCard && (
|
||||||
<Tools
|
<Tools
|
||||||
data={data}
|
data={data}
|
||||||
|
|||||||
@@ -76,17 +76,7 @@ const ParseTip: React.FC<Props> = ({
|
|||||||
const entityAlias = entity?.alias?.[0]?.split('.')?.[0];
|
const entityAlias = entity?.alias?.[0]?.split('.')?.[0];
|
||||||
const entityName = elementMatches?.find(item => item.element?.type === 'ID')?.element.name;
|
const entityName = elementMatches?.find(item => item.element?.type === 'ID')?.element.name;
|
||||||
|
|
||||||
const pluginName = properties?.CONTEXT?.plugin?.name;
|
const { type: agentType, name: agentName } = properties || {};
|
||||||
|
|
||||||
const modeName = pluginName
|
|
||||||
? '调插件'
|
|
||||||
: queryMode.includes('METRIC')
|
|
||||||
? '算指标'
|
|
||||||
: queryMode === 'ENTITY_DETAIL'
|
|
||||||
? '查明细'
|
|
||||||
: queryMode === 'ENTITY_LIST_FILTER'
|
|
||||||
? '做圈选'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const fields =
|
const fields =
|
||||||
queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems;
|
queryMode === 'ENTITY_DETAIL' ? dimensionItems?.concat(metrics || []) : dimensionItems;
|
||||||
@@ -101,11 +91,10 @@ const ParseTip: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{index !== undefined && <div>{index + 1}.</div>}
|
{index !== undefined && <div>{index + 1}.</div>}
|
||||||
{!pluginName && isOptions && <div className={`${prefixCls}-mode-name`}>{modeName}:</div>}
|
{!!agentType ? (
|
||||||
{!!pluginName ? (
|
|
||||||
<div className={`${prefixCls}-tip-item`}>
|
<div className={`${prefixCls}-tip-item`}>
|
||||||
将由问答插件
|
将由{agentType === 'plugin' ? '插件' : '内置'}工具
|
||||||
<span className={itemValueClass}>{pluginName}</span>来解答
|
<span className={itemValueClass}>{agentName}</span>来解答
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -123,7 +112,7 @@ const ParseTip: React.FC<Props> = ({
|
|||||||
<div className={itemValueClass}>{modelName}</div>
|
<div className={itemValueClass}>{modelName}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{modeName === '算指标' && metric && (
|
{metric && (
|
||||||
<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}>{metric.name}</div>
|
<div className={itemValueClass}>{metric.name}</div>
|
||||||
@@ -153,9 +142,13 @@ const ParseTip: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{['METRIC_FILTER', 'METRIC_ENTITY', 'ENTITY_DETAIL', 'ENTITY_LIST_FILTER'].includes(
|
{[
|
||||||
queryMode
|
'METRIC_FILTER',
|
||||||
) &&
|
'METRIC_ENTITY',
|
||||||
|
'ENTITY_DETAIL',
|
||||||
|
'ENTITY_LIST_FILTER',
|
||||||
|
'ENTITY_ID',
|
||||||
|
].includes(queryMode) &&
|
||||||
dimensionFilters &&
|
dimensionFilters &&
|
||||||
dimensionFilters?.length > 0 && (
|
dimensionFilters?.length > 0 && (
|
||||||
<div className={`${prefixCls}-tip-item`}>
|
<div className={`${prefixCls}-tip-item`}>
|
||||||
@@ -198,10 +191,10 @@ const ParseTip: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const pluginName = parseInfoOptions[0]?.properties?.CONTEXT?.plugin?.name;
|
const agentType = parseInfoOptions[0]?.properties?.type;
|
||||||
tipNode = (
|
tipNode = (
|
||||||
<div className={`${prefixCls}-tip`}>
|
<div className={`${prefixCls}-tip`}>
|
||||||
<div>{!!pluginName ? '您的问题' : '您的问题解析为:'}</div>
|
<div>{!!agentType ? '您的问题' : '您的问题解析为:'}</div>
|
||||||
{getTipNode(parseInfoOptions[0])}
|
{getTipNode(parseInfoOptions[0])}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import { PARSE_ERROR_TIP, PREFIX_CLS, SEARCH_EXCEPTION_TIP } from '../../common/
|
|||||||
import IconFont from '../IconFont';
|
import IconFont from '../IconFont';
|
||||||
import ParseTip from './ParseTip';
|
import ParseTip from './ParseTip';
|
||||||
import ExecuteItem from './ExecuteItem';
|
import ExecuteItem from './ExecuteItem';
|
||||||
|
import { isMobile } from '../../utils/utils';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
msg: string;
|
msg: string;
|
||||||
conversationId?: number;
|
conversationId?: number;
|
||||||
modelId?: number;
|
modelId?: number;
|
||||||
|
agentId?: number;
|
||||||
filter?: any[];
|
filter?: any[];
|
||||||
isLastMessage?: boolean;
|
isLastMessage?: boolean;
|
||||||
msgData?: MsgDataType;
|
msgData?: MsgDataType;
|
||||||
@@ -24,6 +27,7 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
msg,
|
msg,
|
||||||
conversationId,
|
conversationId,
|
||||||
modelId,
|
modelId,
|
||||||
|
agentId,
|
||||||
filter,
|
filter,
|
||||||
isLastMessage,
|
isLastMessage,
|
||||||
isMobileMode,
|
isMobileMode,
|
||||||
@@ -41,7 +45,7 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
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 [entitySwitching, setEntitySwitching] = useState(false);
|
const [entitySwitchLoading, setEntitySwitchLoading] = useState(false);
|
||||||
|
|
||||||
const [chartIndex, setChartIndex] = useState(0);
|
const [chartIndex, setChartIndex] = useState(0);
|
||||||
|
|
||||||
@@ -76,6 +80,7 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
) => {
|
) => {
|
||||||
setExecuteMode(true);
|
setExecuteMode(true);
|
||||||
setExecuteLoading(true);
|
setExecuteLoading(true);
|
||||||
|
try {
|
||||||
const { data } = await chatExecute(msg, conversationId!, parseInfoValue);
|
const { data } = await chatExecute(msg, conversationId!, parseInfoValue);
|
||||||
setExecuteLoading(false);
|
setExecuteLoading(false);
|
||||||
const valid = updateData(data);
|
const valid = updateData(data);
|
||||||
@@ -102,11 +107,15 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
valid
|
valid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setExecuteLoading(false);
|
||||||
|
setExecuteTip(SEARCH_EXCEPTION_TIP);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSendMsg = async () => {
|
const onSendMsg = async () => {
|
||||||
setParseLoading(true);
|
setParseLoading(true);
|
||||||
const { data: parseData } = await chatParse(msg, conversationId, modelId, filter);
|
const { data: parseData } = await chatParse(msg, conversationId, modelId, agentId, filter);
|
||||||
setParseLoading(false);
|
setParseLoading(false);
|
||||||
const { code, data } = parseData || {};
|
const { code, data } = parseData || {};
|
||||||
const { state, selectedParses } = data || {};
|
const { state, selectedParses } = data || {};
|
||||||
@@ -115,10 +124,9 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
state === ParseStateEnum.FAILED ||
|
state === ParseStateEnum.FAILED ||
|
||||||
selectedParses == null ||
|
selectedParses == null ||
|
||||||
selectedParses.length === 0 ||
|
selectedParses.length === 0 ||
|
||||||
(selectedParses.length === 1 &&
|
(selectedParses.length > 0 &&
|
||||||
!selectedParses[0]?.modelName &&
|
!selectedParses[0]?.properties?.type &&
|
||||||
!selectedParses[0]?.properties?.CONTEXT?.plugin?.name &&
|
!selectedParses[0]?.queryMode)
|
||||||
selectedParses[0]?.queryMode !== 'WEB_PAGE')
|
|
||||||
) {
|
) {
|
||||||
setParseTip(PARSE_ERROR_TIP);
|
setParseTip(PARSE_ERROR_TIP);
|
||||||
return;
|
return;
|
||||||
@@ -146,9 +154,9 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
}, [msg, msgData]);
|
}, [msg, msgData]);
|
||||||
|
|
||||||
const onSwitchEntity = async (entityId: string) => {
|
const onSwitchEntity = async (entityId: string) => {
|
||||||
setEntitySwitching(true);
|
setEntitySwitchLoading(true);
|
||||||
const res = await switchEntity(entityId, data?.chatContext?.modelId, conversationId || 0);
|
const res = await switchEntity(entityId, data?.chatContext?.modelId, conversationId || 0);
|
||||||
setEntitySwitching(false);
|
setEntitySwitchLoading(false);
|
||||||
setData(res.data.data);
|
setData(res.data.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -164,11 +172,15 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const contentClass = classNames(`${prefixCls}-content`, {
|
||||||
|
[`${prefixCls}-content-mobile`]: isMobile,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={prefixCls}>
|
<div className={prefixCls}>
|
||||||
<div className={`${prefixCls}-section`}>
|
<div className={`${prefixCls}-section`}>
|
||||||
<IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />
|
{!isMobile && <IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />}
|
||||||
<div className={`${prefixCls}-content`}>
|
<div className={contentClass}>
|
||||||
<ParseTip
|
<ParseTip
|
||||||
parseLoading={parseLoading}
|
parseLoading={parseLoading}
|
||||||
parseInfoOptions={parseOptions || parseInfoOptions.slice(0, 1)}
|
parseInfoOptions={parseOptions || parseInfoOptions.slice(0, 1)}
|
||||||
@@ -181,11 +193,12 @@ const ChatItem: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
{executeMode && data?.queryMode !== 'WEB_PAGE' && (
|
{executeMode && data?.queryMode !== 'WEB_PAGE' && (
|
||||||
<div className={`${prefixCls}-section`}>
|
<div className={`${prefixCls}-section`}>
|
||||||
<IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />
|
{!isMobile && <IconFont type="icon-zhinengsuanfa" className={`${prefixCls}-avatar`} />}
|
||||||
<div className={`${prefixCls}-content`}>
|
<div className={contentClass}>
|
||||||
<ExecuteItem
|
<ExecuteItem
|
||||||
question={msg}
|
question={msg}
|
||||||
executeLoading={executeLoading}
|
executeLoading={executeLoading}
|
||||||
|
entitySwitchLoading={entitySwitchLoading}
|
||||||
executeTip={executeTip}
|
executeTip={executeTip}
|
||||||
chartIndex={chartIndex}
|
chartIndex={chartIndex}
|
||||||
data={data}
|
data={data}
|
||||||
|
|||||||
@@ -117,6 +117,10 @@
|
|||||||
width: calc(100% - 50px);
|
width: calc(100% - 50px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-content-mobile {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&-metric-info-list {
|
&-metric-info-list {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -136,7 +140,6 @@
|
|||||||
|
|
||||||
&-typing-bubble {
|
&-typing-bubble {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
// padding: 16px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-text-bubble {
|
&-text-bubble {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { PREFIX_CLS } from '../../../common/constants';
|
|||||||
type Props = {
|
type Props = {
|
||||||
position: 'left' | 'right';
|
position: 'left' | 'right';
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
|
maxWidth?: number | string;
|
||||||
height?: number | string;
|
height?: number | string;
|
||||||
title?: string;
|
title?: string;
|
||||||
followQuestions?: string[];
|
followQuestions?: string[];
|
||||||
@@ -17,6 +18,7 @@ type Props = {
|
|||||||
|
|
||||||
const Message: React.FC<Props> = ({
|
const Message: React.FC<Props> = ({
|
||||||
width,
|
width,
|
||||||
|
maxWidth,
|
||||||
height,
|
height,
|
||||||
children,
|
children,
|
||||||
bubbleClassName,
|
bubbleClassName,
|
||||||
@@ -38,7 +40,7 @@ const Message: React.FC<Props> = ({
|
|||||||
<div className={`${prefixCls}-body`}>
|
<div className={`${prefixCls}-body`}>
|
||||||
<div
|
<div
|
||||||
className={`${prefixCls}-bubble${bubbleClassName ? ` ${bubbleClassName}` : ''}`}
|
className={`${prefixCls}-bubble${bubbleClassName ? ` ${bubbleClassName}` : ''}`}
|
||||||
style={{ width, height }}
|
style={{ width, height, maxWidth }}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -43,7 +43,11 @@ const MetricCard: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={prefixCls}>
|
<div className={prefixCls}>
|
||||||
<div className={`${prefixCls}-top-bar`}>
|
<div className={`${prefixCls}-top-bar`}>
|
||||||
|
{indicatorColumn?.name ? (
|
||||||
<div className={`${prefixCls}-indicator-name`}>{indicatorColumn?.name}</div>
|
<div className={`${prefixCls}-indicator-name`}>{indicatorColumn?.name}</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ height: 32 }} />
|
||||||
|
)}
|
||||||
{(hasFilterSection || drillDownDimension) && (
|
{(hasFilterSection || drillDownDimension) && (
|
||||||
<div className={`${prefixCls}-filter-section-wrapper`}>
|
<div className={`${prefixCls}-filter-section-wrapper`}>
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
&-drill-down-dimensions {
|
&-drill-down-dimensions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -38px;
|
bottom: -44px;
|
||||||
left: 0;
|
left: -16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,25 +19,19 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApplyAuth }) => {
|
const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApplyAuth }) => {
|
||||||
const { queryColumns, queryResults, entityInfo, chatContext, queryMode, aggregateInfo } = data;
|
const { entityInfo, chatContext, queryMode } = data;
|
||||||
|
const { dateInfo, dimensionFilters, elementMatches } = chatContext || {};
|
||||||
const { dateMode, unit } = chatContext?.dateInfo || {};
|
const { dateMode, unit } = dateInfo || {};
|
||||||
|
|
||||||
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY;
|
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY;
|
||||||
const initialDateOption = dateOptions.find((option: any) => {
|
|
||||||
return dateMode === 'RECENT' && option.value === unit;
|
|
||||||
})?.value;
|
|
||||||
|
|
||||||
const [columns, setColumns] = useState<ColumnType[]>(queryColumns || []);
|
const [columns, setColumns] = useState<ColumnType[]>([]);
|
||||||
const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER');
|
const [activeMetricField, setActiveMetricField] = useState<FieldType>();
|
||||||
|
const [dataSource, setDataSource] = useState<any[]>([]);
|
||||||
const [activeMetricField, setActiveMetricField] = useState<FieldType>(chatContext.metrics?.[0]);
|
const [currentDateOption, setCurrentDateOption] = useState<number>();
|
||||||
const [dataSource, setDataSource] = useState<any[]>(queryResults);
|
const [dimensions, setDimensions] = useState<FieldType[]>();
|
||||||
const [currentDateOption, setCurrentDateOption] = useState<number>(initialDateOption);
|
|
||||||
const [dimensions, setDimensions] = useState<FieldType[]>(chatContext?.dimensions);
|
|
||||||
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
|
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
|
||||||
const [aggregateInfoValue, setAggregateInfoValue] = useState<any>(aggregateInfo);
|
const [aggregateInfoValue, setAggregateInfoValue] = useState<any>();
|
||||||
const [dateModeValue, setDateModeValue] = useState(dateMode);
|
const [dateModeValue, setDateModeValue] = useState<any>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const dateField: any = columns.find(
|
const dateField: any = columns.find(
|
||||||
@@ -47,9 +41,31 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
|||||||
const categoryColumnName =
|
const categoryColumnName =
|
||||||
columns.find((column: any) => column.showType === 'CATEGORY')?.nameEn || '';
|
columns.find((column: any) => column.showType === 'CATEGORY')?.nameEn || '';
|
||||||
|
|
||||||
|
const entityId = dimensionFilters?.length > 0 ? dimensionFilters[0].value : undefined;
|
||||||
|
const entityName = elementMatches?.find((item: any) => item.element?.type === 'ID')?.element
|
||||||
|
?.name;
|
||||||
|
|
||||||
|
const isEntityMode =
|
||||||
|
(queryMode === 'ENTITY_LIST_FILTER' || queryMode === 'METRIC_ENTITY') &&
|
||||||
|
typeof entityId === 'string' &&
|
||||||
|
entityName !== undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const { queryColumns, queryResults, chatContext, aggregateInfo } = data;
|
||||||
|
|
||||||
|
const initialDateOption = dateOptions.find((option: any) => {
|
||||||
|
return dateMode === 'RECENT' && option.value === unit;
|
||||||
|
})?.value;
|
||||||
|
|
||||||
|
setColumns(queryColumns || []);
|
||||||
|
setActiveMetricField(chatContext?.metrics?.[0]);
|
||||||
setDataSource(queryResults);
|
setDataSource(queryResults);
|
||||||
}, [queryResults]);
|
setCurrentDateOption(initialDateOption);
|
||||||
|
setDimensions(chatContext?.dimensions);
|
||||||
|
setDrillDownDimension(undefined);
|
||||||
|
setAggregateInfoValue(aggregateInfo);
|
||||||
|
setDateModeValue(chatContext?.dateInfo?.dateMode);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryMode === 'METRIC_GROUPBY') {
|
if (queryMode === 'METRIC_GROUPBY') {
|
||||||
@@ -117,14 +133,14 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const currentMetricField = columns.find((column: any) => column.showType === 'NUMBER');
|
||||||
|
|
||||||
if (!currentMetricField) {
|
if (!currentMetricField) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefixCls = `${CLS_PREFIX}-metric-trend`;
|
const prefixCls = `${CLS_PREFIX}-metric-trend`;
|
||||||
|
|
||||||
const { dimensionFilters } = chatContext || {};
|
|
||||||
|
|
||||||
const hasFilterSection = dimensionFilters?.length > 0;
|
const hasFilterSection = dimensionFilters?.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -174,6 +190,8 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<div className={`${prefixCls}-content`}>
|
||||||
{aggregateInfoValue?.metricInfos?.length > 0 && (
|
{aggregateInfoValue?.metricInfos?.length > 0 && (
|
||||||
<MetricInfo aggregateInfo={aggregateInfoValue} />
|
<MetricInfo aggregateInfo={aggregateInfoValue} />
|
||||||
)}
|
)}
|
||||||
@@ -204,7 +222,6 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<Spin spinning={loading}>
|
|
||||||
{dataSource?.length === 1 || chartIndex % 2 === 1 ? (
|
{dataSource?.length === 1 || chartIndex % 2 === 1 ? (
|
||||||
<Table data={{ ...data, queryResults: dataSource }} onApplyAuth={onApplyAuth} />
|
<Table data={{ ...data, queryResults: dataSource }} onApplyAuth={onApplyAuth} />
|
||||||
) : (
|
) : (
|
||||||
@@ -218,8 +235,8 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
|||||||
onApplyAuth={onApplyAuth}
|
onApplyAuth={onApplyAuth}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Spin>
|
</div>
|
||||||
{queryMode.includes('METRIC') && (
|
{queryMode.includes('METRIC') && !isEntityMode && (
|
||||||
<DrillDownDimensions
|
<DrillDownDimensions
|
||||||
modelId={chatContext.modelId}
|
modelId={chatContext.modelId}
|
||||||
drillDownDimension={drillDownDimension}
|
drillDownDimension={drillDownDimension}
|
||||||
@@ -227,6 +244,7 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
|||||||
onSelectDimension={onSelectDimension}
|
onSelectDimension={onSelectDimension}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -48,6 +48,13 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
row-gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
&-indicator {
|
&-indicator {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import MetricCard from './MetricCard';
|
|||||||
import MetricTrend from './MetricTrend';
|
import MetricTrend from './MetricTrend';
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import { ColumnType, DrillDownDimensionType, MsgDataType } from '../../common/type';
|
import { ColumnType, DrillDownDimensionType, MsgDataType } from '../../common/type';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { queryData } from '../../service';
|
import { queryData } from '../../service';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -25,6 +25,11 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
|||||||
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
|
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setColumns(queryColumns);
|
||||||
|
setDataSource(queryResults);
|
||||||
|
}, [queryColumns, queryResults]);
|
||||||
|
|
||||||
if (!queryColumns || !queryResults) {
|
if (!queryColumns || !queryResults) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -35,14 +40,15 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
|||||||
const metricFields = columns.filter(item => item.showType === 'NUMBER');
|
const metricFields = columns.filter(item => item.showType === 'NUMBER');
|
||||||
|
|
||||||
const isMetricCard =
|
const isMetricCard =
|
||||||
queryMode.includes('METRIC') &&
|
(queryMode.includes('METRIC') ||
|
||||||
|
(queryMode === 'DSL' && singleData && metricFields.length === 1 && columns.length === 1)) &&
|
||||||
(singleData || chatContext?.dateInfo?.startDate === chatContext?.dateInfo?.endDate);
|
(singleData || chatContext?.dateInfo?.startDate === chatContext?.dateInfo?.endDate);
|
||||||
|
|
||||||
const isText =
|
const isText =
|
||||||
columns.length === 1 &&
|
columns.length === 1 &&
|
||||||
columns[0].showType === 'CATEGORY' &&
|
columns[0].showType === 'CATEGORY' &&
|
||||||
!queryMode.includes('METRIC') &&
|
((!queryMode.includes('METRIC') && !queryMode.includes('ENTITY')) ||
|
||||||
!queryMode.includes('ENTITY') &&
|
queryMode === 'METRIC_INTERPRET') &&
|
||||||
singleData;
|
singleData;
|
||||||
|
|
||||||
const onLoadData = async (value: any) => {
|
const onLoadData = async (value: any) => {
|
||||||
@@ -68,9 +74,43 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
|||||||
|
|
||||||
const getMsgContent = () => {
|
const getMsgContent = () => {
|
||||||
if (isText) {
|
if (isText) {
|
||||||
|
let text = dataSource[0][columns[0].nameEn];
|
||||||
|
let htmlCode: string;
|
||||||
|
const match = text.match(/```html([\s\S]*?)```/);
|
||||||
|
htmlCode = match && match[1].trim();
|
||||||
|
if (htmlCode) {
|
||||||
|
text = text.replace(/```html([\s\S]*?)```/, '');
|
||||||
|
}
|
||||||
|
let scriptCode: string;
|
||||||
|
let scriptSrc: string;
|
||||||
|
if (htmlCode) {
|
||||||
|
scriptSrc = htmlCode.match(/<script src="([\s\S]*?)"><\/script>/)?.[1] || '';
|
||||||
|
scriptCode =
|
||||||
|
htmlCode.match(/<script type="text\/javascript">([\s\S]*?)<\/script>/)?.[1] || '';
|
||||||
|
if (scriptSrc) {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = scriptSrc;
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
|
if (scriptCode) {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.innerHTML = scriptCode;
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div style={{ lineHeight: '24px', width: 'fit-content' }}>
|
<div
|
||||||
{dataSource[0][columns[0].nameEn]}
|
style={{
|
||||||
|
lineHeight: '24px',
|
||||||
|
width: 'fit-content',
|
||||||
|
maxWidth: '100%',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{htmlCode ? <pre>{text}</pre> : text}
|
||||||
|
{!!htmlCode && <div dangerouslySetInnerHTML={{ __html: htmlCode }} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -103,6 +143,7 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (categoryField?.length > 0 && metricFields?.length > 0) {
|
||||||
return (
|
return (
|
||||||
<Bar
|
<Bar
|
||||||
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
|
data={{ ...data, queryColumns: columns, queryResults: dataSource }}
|
||||||
@@ -112,6 +153,8 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
|||||||
onSelectDimension={onSelectDimension}
|
onSelectDimension={onSelectDimension}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
let width = '100%';
|
let width = '100%';
|
||||||
@@ -135,6 +178,7 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
|||||||
title={question}
|
title={question}
|
||||||
isMobileMode={isMobileMode}
|
isMobileMode={isMobileMode}
|
||||||
width={width}
|
width={width}
|
||||||
|
maxWidth={isText && !isMobile ? '80%' : undefined}
|
||||||
queryMode={queryMode}
|
queryMode={queryMode}
|
||||||
>
|
>
|
||||||
{getMsgContent()}
|
{getMsgContent()}
|
||||||
|
|||||||
@@ -29,6 +29,15 @@ const Tools: React.FC<Props> = ({
|
|||||||
const { queryColumns, queryResults, queryId, chatContext, queryMode, entityInfo } = data || {};
|
const { queryColumns, queryResults, queryId, chatContext, queryMode, entityInfo } = data || {};
|
||||||
const [score, setScore] = useState(scoreValue || 0);
|
const [score, setScore] = useState(scoreValue || 0);
|
||||||
|
|
||||||
|
const { dimensionFilters, elementMatches } = data.chatContext;
|
||||||
|
|
||||||
|
const entityId = dimensionFilters?.length > 0 ? dimensionFilters[0].value : undefined;
|
||||||
|
const entityName = elementMatches?.find((item: any) => item.element?.type === 'ID')?.element
|
||||||
|
?.name;
|
||||||
|
|
||||||
|
const isEntityMode =
|
||||||
|
queryMode === 'ENTITY_LIST_FILTER' && typeof entityId === 'string' && entityName !== undefined;
|
||||||
|
|
||||||
const prefixCls = `${CLS_PREFIX}-tools`;
|
const prefixCls = `${CLS_PREFIX}-tools`;
|
||||||
|
|
||||||
const singleData = queryResults.length === 1;
|
const singleData = queryResults.length === 1;
|
||||||
@@ -41,7 +50,8 @@ const Tools: React.FC<Props> = ({
|
|||||||
queryColumns[0].showType === 'CATEGORY' &&
|
queryColumns[0].showType === 'CATEGORY' &&
|
||||||
queryResults?.length === 1) ||
|
queryResults?.length === 1) ||
|
||||||
(!queryMode.includes('METRIC') && !queryMode.includes('ENTITY')) ||
|
(!queryMode.includes('METRIC') && !queryMode.includes('ENTITY')) ||
|
||||||
isMetricCard;
|
isMetricCard ||
|
||||||
|
isEntityMode;
|
||||||
|
|
||||||
const changeChart = () => {
|
const changeChart = () => {
|
||||||
onChangeChart();
|
onChangeChart();
|
||||||
@@ -75,7 +85,7 @@ const Tools: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={prefixCls}>
|
<div className={prefixCls}>
|
||||||
{/* {isLastMessage && chatContext?.modelId && entityInfo?.entityId && (
|
{isLastMessage && chatContext?.modelId && entityInfo?.entityId && (
|
||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
<RecommendOptions
|
<RecommendOptions
|
||||||
@@ -93,7 +103,7 @@ const Tools: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<Button shape="round">切换其他匹配内容</Button>
|
<Button shape="round">切换其他匹配内容</Button>
|
||||||
</Popover>
|
</Popover>
|
||||||
)} */}
|
)}
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<>
|
<>
|
||||||
{queryMode === 'METRIC_FILTER' && (
|
{queryMode === 'METRIC_FILTER' && (
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ const Chat = () => {
|
|||||||
<ChatItem
|
<ChatItem
|
||||||
msg={msg}
|
msg={msg}
|
||||||
// msgData={data}
|
// msgData={data}
|
||||||
|
agentId={6}
|
||||||
onMsgDataLoaded={onMsgDataLoaded}
|
onMsgDataLoaded={onMsgDataLoaded}
|
||||||
isLastMessage
|
isLastMessage
|
||||||
isMobileMode
|
isMobileMode
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const axiosInstance: AxiosInstance = axios.create({
|
|||||||
// 设置基本URL,所有请求都会使用这个URL作为前缀
|
// 设置基本URL,所有请求都会使用这个URL作为前缀
|
||||||
baseURL: '',
|
baseURL: '',
|
||||||
// 设置请求超时时间(毫秒)
|
// 设置请求超时时间(毫秒)
|
||||||
timeout: 60000,
|
timeout: 120000,
|
||||||
// 设置请求头
|
// 设置请求头
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ export function chatQuery(queryText: string, chatId?: number, modelId?: number,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function chatParse(queryText: string, chatId?: number, modelId?: number, filters?: any[]) {
|
export function chatParse(queryText: string, chatId?: number, modelId?: number, agentId?: number, filters?: any[]) {
|
||||||
return axios.post<Result<ParseDataType>>(`${prefix}/chat/query/parse`, {
|
return axios.post<Result<ParseDataType>>(`${prefix}/chat/query/parse`, {
|
||||||
queryText,
|
queryText,
|
||||||
chatId: chatId || DEFAULT_CHAT_ID,
|
chatId: chatId || DEFAULT_CHAT_ID,
|
||||||
modelId,
|
modelId,
|
||||||
|
agentId,
|
||||||
queryFilters: filters ? {
|
queryFilters: filters ? {
|
||||||
filters
|
filters
|
||||||
} : undefined,
|
} : undefined,
|
||||||
@@ -63,10 +64,6 @@ export function queryContext(queryText: string, chatId?: number) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function querySuggestionInfo(modelId: number) {
|
|
||||||
return axios.get<Result<any>>(`${prefix}/chat/recommend/${modelId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getHistoryMsg(current: number, chatId: number = DEFAULT_CHAT_ID, pageSize: number = 10) {
|
export function getHistoryMsg(current: number, chatId: number = DEFAULT_CHAT_ID, pageSize: number = 10) {
|
||||||
return axios.post<Result<HistoryType>>(`${prefix}/chat/manage/pageQueryInfo?chatId=${chatId}`, {
|
return axios.post<Result<HistoryType>>(`${prefix}/chat/manage/pageQueryInfo?chatId=${chatId}`, {
|
||||||
current,
|
current,
|
||||||
@@ -74,22 +71,6 @@ export function getHistoryMsg(current: number, chatId: number = DEFAULT_CHAT_ID,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queryMetricInfo(data: any) {
|
|
||||||
return axios.get(`/semantic/metric/getMetric/${data.classId}/${data.uniqueId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRelatedDimensionFromStatInfo(data: any) {
|
|
||||||
return axios.get(
|
|
||||||
`/semantic/metric/getRelatedDimensionFromStatInfo/${data.classId}/${data.uniqueId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMetricQueryInfo(data: any) {
|
|
||||||
return axios.get<any>(
|
|
||||||
`/openapi/bd-bi/api/polaris/intelligentQuery/getMetricQueryInfo/${data.classId}/${data.metricName}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveConversation(chatName: string) {
|
export function saveConversation(chatName: string) {
|
||||||
return axios.post<Result<any>>(`${prefix}/chat/manage/save?chatName=${chatName}`);
|
return axios.post<Result<any>>(`${prefix}/chat/manage/save?chatName=${chatName}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ const ROUTES = [
|
|||||||
component: './ChatPlugin',
|
component: './ChatPlugin',
|
||||||
envEnableList: [ENV_KEY.CHAT],
|
envEnableList: [ENV_KEY.CHAT],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/agent',
|
||||||
|
name: 'agent',
|
||||||
|
component: './Agent',
|
||||||
|
envEnableList: [ENV_KEY.CHAT],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/semanticModel/model/:domainId?/:modelId?/:menuKey?',
|
path: '/semanticModel/model/:domainId?/:modelId?/:menuKey?',
|
||||||
component: './SemanticModel/DomainManager',
|
component: './SemanticModel/DomainManager',
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
"react-split-pane": "^2.0.3",
|
"react-split-pane": "^2.0.3",
|
||||||
"react-syntax-highlighter": "^15.4.3",
|
"react-syntax-highlighter": "^15.4.3",
|
||||||
"sql-formatter": "^2.3.3",
|
"sql-formatter": "^2.3.3",
|
||||||
"supersonic-chat-sdk": "^0.3.0",
|
"supersonic-chat-sdk": "^0.4.32",
|
||||||
"umi": "3.5",
|
"umi": "3.5",
|
||||||
"umi-request": "^1.0.8"
|
"umi-request": "^1.0.8"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const TOKEN_KEY = AUTH_TOKEN_KEY;
|
|||||||
|
|
||||||
const replaceRoute = '/';
|
const replaceRoute = '/';
|
||||||
|
|
||||||
const getRuningEnv = async () => {
|
const getRunningEnv = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${publicPath}supersonic.config.json`);
|
const response = await fetch(`${publicPath}supersonic.config.json`);
|
||||||
const config = await response.json();
|
const config = await response.json();
|
||||||
@@ -72,7 +72,6 @@ export async function getInitialState(): Promise<{
|
|||||||
codeList?: string[];
|
codeList?: string[];
|
||||||
authCodes?: string[];
|
authCodes?: string[];
|
||||||
}> {
|
}> {
|
||||||
// await getRuningEnv();
|
|
||||||
const fetchUserInfo = async () => {
|
const fetchUserInfo = async () => {
|
||||||
try {
|
try {
|
||||||
const { code, data } = await queryCurrentUser();
|
const { code, data } = await queryCurrentUser();
|
||||||
@@ -112,8 +111,9 @@ export async function getInitialState(): Promise<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function patchRoutes({ routes }) {
|
export async function patchRoutes({ routes }) {
|
||||||
const config = await getRuningEnv();
|
const config = await getRunningEnv();
|
||||||
if (config && config.env) {
|
if (config && config.env) {
|
||||||
|
window.RUNNING_ENV = config.env;
|
||||||
const { env } = config;
|
const { env } = config;
|
||||||
const target = routes[0].routes;
|
const target = routes[0].routes;
|
||||||
if (env) {
|
if (env) {
|
||||||
@@ -123,6 +123,14 @@ export async function patchRoutes({ routes }) {
|
|||||||
// 写入根据环境转换过的的route
|
// 写入根据环境转换过的的route
|
||||||
target.push(...envRoutes);
|
target.push(...envRoutes);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const target = routes[0].routes;
|
||||||
|
// start-standalone模式不存在env,在此模式下不显示chatSetting
|
||||||
|
const envRoutes = target.filter((item: any) => {
|
||||||
|
return !['chatSetting'].includes(item.name);
|
||||||
|
});
|
||||||
|
target.splice(0, 99);
|
||||||
|
target.push(...envRoutes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ export default {
|
|||||||
'menu.chatPlugin': '问答插件',
|
'menu.chatPlugin': '问答插件',
|
||||||
'menu.login': '登录',
|
'menu.login': '登录',
|
||||||
'menu.chat': '问答对话',
|
'menu.chat': '问答对话',
|
||||||
|
'menu.agent': '问答助理'
|
||||||
};
|
};
|
||||||
|
|||||||
109
webapp/packages/supersonic-fe/src/pages/Agent/AgentModal.tsx
Normal file
109
webapp/packages/supersonic-fe/src/pages/Agent/AgentModal.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { Form, Modal, Input, Button, Switch } from 'antd';
|
||||||
|
import { AgentType } from './type';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import styles from './style.less';
|
||||||
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { uuid } from '@/utils/utils';
|
||||||
|
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
editAgent?: AgentType;
|
||||||
|
onSaveAgent: (agent: AgentType) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AgentModal: React.FC<Props> = ({ editAgent, onSaveAgent, onCancel }) => {
|
||||||
|
const [saveLoading, setSaveLoading] = useState(false);
|
||||||
|
const [examples, setExamples] = useState<{ id: string; question?: string }[]>([]);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editAgent) {
|
||||||
|
form.setFieldsValue({ ...editAgent, enableSearch: editAgent.enableSearch !== 0 });
|
||||||
|
if (editAgent.examples) {
|
||||||
|
setExamples(editAgent.examples.map((question) => ({ id: uuid(), question })));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}, [editAgent]);
|
||||||
|
|
||||||
|
const layout = {
|
||||||
|
labelCol: { span: 6 },
|
||||||
|
wrapperCol: { span: 14 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
setSaveLoading(true);
|
||||||
|
await onSaveAgent({
|
||||||
|
id: editAgent?.id,
|
||||||
|
...(editAgent || {}),
|
||||||
|
...values,
|
||||||
|
examples: examples.map((example) => example.question),
|
||||||
|
enableSearch: values.enableSearch ? 1 : 0,
|
||||||
|
});
|
||||||
|
setSaveLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open
|
||||||
|
title={editAgent ? '编辑助理' : '新建助理'}
|
||||||
|
confirmLoading={saveLoading}
|
||||||
|
width={800}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
>
|
||||||
|
<Form {...layout} form={form} initialValues={{ enableSearch: true }}>
|
||||||
|
<FormItem name="name" label="名称" rules={[{ required: true, message: '请输入助理名称' }]}>
|
||||||
|
<Input placeholder="请输入助理名称" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="enableSearch" label="支持联想" valuePropName="checked">
|
||||||
|
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="examples" label="示例问题">
|
||||||
|
<div className={styles.paramsSection}>
|
||||||
|
{examples.map((example) => {
|
||||||
|
const { id, question } = example;
|
||||||
|
return (
|
||||||
|
<div className={styles.filterRow} key={id}>
|
||||||
|
<Input
|
||||||
|
placeholder="示例问题"
|
||||||
|
value={question}
|
||||||
|
className={styles.questionExample}
|
||||||
|
onChange={(e) => {
|
||||||
|
example.question = e.target.value;
|
||||||
|
setExamples([...examples]);
|
||||||
|
}}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
<DeleteOutlined
|
||||||
|
onClick={() => {
|
||||||
|
setExamples(examples.filter((item) => item.id !== id));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setExamples([...examples, { id: uuid() }]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
新增示例问题
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="description" label="描述">
|
||||||
|
<TextArea placeholder="请输入助理描述" />
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AgentModal;
|
||||||
144
webapp/packages/supersonic-fe/src/pages/Agent/AgentsSection.tsx
Normal file
144
webapp/packages/supersonic-fe/src/pages/Agent/AgentsSection.tsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { DeleteOutlined, EditOutlined, PlusOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Input, Popconfirm, Switch } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import styles from './style.less';
|
||||||
|
import { AgentType } from './type';
|
||||||
|
|
||||||
|
const { Search } = Input;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
agents: AgentType[];
|
||||||
|
currentAgent?: AgentType;
|
||||||
|
loading: boolean;
|
||||||
|
onSelectAgent: (agent: AgentType) => void;
|
||||||
|
onDeleteAgent: (id: number) => void;
|
||||||
|
onEditAgent: (agent?: AgentType) => void;
|
||||||
|
onSaveAgent: (agent: AgentType, noTip?: boolean) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AgentsSection: React.FC<Props> = ({
|
||||||
|
agents,
|
||||||
|
currentAgent,
|
||||||
|
onSelectAgent,
|
||||||
|
onDeleteAgent,
|
||||||
|
onEditAgent,
|
||||||
|
onSaveAgent,
|
||||||
|
}) => {
|
||||||
|
// const [searchName, setSearchName] = useState('');
|
||||||
|
const [showAgents, setShowAgents] = useState<AgentType[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShowAgents(agents);
|
||||||
|
}, [agents]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.agentsSection}>
|
||||||
|
{/* <div className={styles.sectionTitle}>问答助理</div> */}
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.searchBar}>
|
||||||
|
{/* <Search
|
||||||
|
placeholder="请输入助理名称搜索"
|
||||||
|
className={styles.searchControl}
|
||||||
|
value={searchName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearchName(e.target.value);
|
||||||
|
}}
|
||||||
|
onSearch={(value) => {
|
||||||
|
setShowAgents(
|
||||||
|
agents.filter((agent) =>
|
||||||
|
agent.name?.trim().toLowerCase().includes(value.trim().toLowerCase()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
onEditAgent(undefined);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
新建助理
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.agentsContainer}>
|
||||||
|
{showAgents.map((agent) => {
|
||||||
|
const agentItemClass = classNames(styles.agentItem, {
|
||||||
|
[styles.agentActive]: agent.id === currentAgent?.id,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={agentItemClass}
|
||||||
|
key={agent.id}
|
||||||
|
onClick={() => {
|
||||||
|
onSelectAgent(agent);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserOutlined className={styles.agentIcon} />
|
||||||
|
<div className={styles.agentContent}>
|
||||||
|
<div className={styles.agentNameBar}>
|
||||||
|
<div className={styles.agentName}>{agent.name}</div>
|
||||||
|
<div className={styles.operateIcons}>
|
||||||
|
<EditOutlined
|
||||||
|
className={styles.operateIcon}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEditAgent(agent);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定删除吗?"
|
||||||
|
onCancel={(e) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
}}
|
||||||
|
onConfirm={(e) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
onDeleteAgent(agent.id!);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteOutlined
|
||||||
|
className={styles.operateIcon}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.bottomBar}>
|
||||||
|
<div className={styles.agentDescription} title={agent.description}>
|
||||||
|
{agent.description}
|
||||||
|
</div>
|
||||||
|
<div className={styles.toggleStatus}>
|
||||||
|
{agent.status === 0 ? (
|
||||||
|
'已禁用'
|
||||||
|
) : (
|
||||||
|
<span className={styles.online}>已启用</span>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
defaultChecked={agent.status === 1}
|
||||||
|
onChange={(value) => {
|
||||||
|
onSaveAgent({ ...agent, status: value ? 1 : 0 }, true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AgentsSection;
|
||||||
264
webapp/packages/supersonic-fe/src/pages/Agent/ToolModal.tsx
Normal file
264
webapp/packages/supersonic-fe/src/pages/Agent/ToolModal.tsx
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
import { Form, Modal, Input, Select, Button } from 'antd';
|
||||||
|
import {
|
||||||
|
AgentToolType,
|
||||||
|
AgentToolTypeEnum,
|
||||||
|
AGENT_TOOL_TYPE_LIST,
|
||||||
|
MetricOptionType,
|
||||||
|
MetricType,
|
||||||
|
ModelType,
|
||||||
|
QUERY_MODE_LIST,
|
||||||
|
} from './type';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import styles from './style.less';
|
||||||
|
import { getLeafList, uuid } from '@/utils/utils';
|
||||||
|
import { getMetricList, getModelList } from './service';
|
||||||
|
import { PluginType } from '../ChatPlugin/type';
|
||||||
|
import { getPluginList } from '../ChatPlugin/service';
|
||||||
|
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
editTool?: AgentToolType;
|
||||||
|
onSaveTool: (tool: AgentToolType) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ToolModal: React.FC<Props> = ({ editTool, onSaveTool, onCancel }) => {
|
||||||
|
const [toolType, setToolType] = useState<AgentToolTypeEnum>();
|
||||||
|
const [modelList, setModelList] = useState<ModelType[]>([]);
|
||||||
|
const [saveLoading, setSaveLoading] = useState(false);
|
||||||
|
const [examples, setExamples] = useState<{ id: string; question?: string }[]>([]);
|
||||||
|
const [metricOptions, setMetricOptions] = useState<MetricOptionType[]>([]);
|
||||||
|
const [modelMetricList, setModelMetricList] = useState<MetricType[]>([]);
|
||||||
|
const [plugins, setPlugins] = useState<PluginType[]>([]);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const initModelList = async () => {
|
||||||
|
const res = await getModelList();
|
||||||
|
setModelList([{ id: -1, name: '默认' }, ...getLeafList(res.data)]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initPluginList = async () => {
|
||||||
|
const res = await getPluginList({});
|
||||||
|
setPlugins(res.data || []);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initModelList();
|
||||||
|
initPluginList();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const initModelMetrics = async (params: any) => {
|
||||||
|
const res = await getMetricList(params[0].modelId);
|
||||||
|
setModelMetricList(res.data.list);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editTool) {
|
||||||
|
form.setFieldsValue({ ...editTool, plugins: editTool.plugins?.[0] });
|
||||||
|
setToolType(editTool.type);
|
||||||
|
setExamples(
|
||||||
|
(editTool.exampleQuestions || []).map((item) => ({ id: uuid(), question: item })),
|
||||||
|
);
|
||||||
|
setMetricOptions(editTool.metricOptions || []);
|
||||||
|
if (editTool.metricOptions && editTool.metricOptions.length > 0) {
|
||||||
|
initModelMetrics(editTool.metricOptions || []);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}, [editTool]);
|
||||||
|
|
||||||
|
const layout = {
|
||||||
|
labelCol: { span: 6 },
|
||||||
|
wrapperCol: { span: 14 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
setSaveLoading(true);
|
||||||
|
await onSaveTool({
|
||||||
|
id: editTool?.id,
|
||||||
|
...values,
|
||||||
|
exampleQuestions: examples.map((item) => item.question).filter((item) => item),
|
||||||
|
plugins: values.plugins ? [values.plugins] : undefined,
|
||||||
|
metricOptions: metricOptions.map((item) => ({ modelId: values.modelId, ...item })),
|
||||||
|
});
|
||||||
|
setSaveLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMetricList = async (value: number) => {
|
||||||
|
if (modelMetricList[value]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await getMetricList(value);
|
||||||
|
setModelMetricList(res.data.list);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open
|
||||||
|
title={editTool ? '编辑工具' : '新建工具'}
|
||||||
|
confirmLoading={saveLoading}
|
||||||
|
width={800}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
>
|
||||||
|
<Form {...layout} form={form}>
|
||||||
|
<FormItem name="type" label="类型" rules={[{ required: true, message: '请选择工具类型' }]}>
|
||||||
|
<Select
|
||||||
|
options={AGENT_TOOL_TYPE_LIST}
|
||||||
|
placeholder="请选择工具类型"
|
||||||
|
onChange={setToolType}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="name" label="名称">
|
||||||
|
<Input placeholder="请输入工具名称" />
|
||||||
|
</FormItem>
|
||||||
|
{toolType === AgentToolTypeEnum.DSL && (
|
||||||
|
<>
|
||||||
|
<FormItem name="modelIds" label="主题域">
|
||||||
|
<Select
|
||||||
|
options={modelList.map((model) => ({ label: model.name, value: model.id }))}
|
||||||
|
placeholder="请选择主题域"
|
||||||
|
mode="multiple"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="exampleQuestions" label="示例问题">
|
||||||
|
<div className={styles.paramsSection}>
|
||||||
|
{examples.map((example) => {
|
||||||
|
const { id, question } = example;
|
||||||
|
return (
|
||||||
|
<div className={styles.filterRow} key={id}>
|
||||||
|
<Input
|
||||||
|
placeholder="示例问题"
|
||||||
|
value={question}
|
||||||
|
className={styles.questionExample}
|
||||||
|
onChange={(e) => {
|
||||||
|
example.question = e.target.value;
|
||||||
|
setExamples([...examples]);
|
||||||
|
}}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
<DeleteOutlined
|
||||||
|
onClick={() => {
|
||||||
|
setExamples(examples.filter((item) => item.id !== id));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setExamples([...examples, { id: uuid() }]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
新增示例问题
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{toolType === AgentToolTypeEnum.INTERPRET && (
|
||||||
|
<>
|
||||||
|
<FormItem name="modelId" label="主题域">
|
||||||
|
<Select
|
||||||
|
options={modelList.map((model) => ({ label: model.name, value: model.id }))}
|
||||||
|
showSearch
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
|
||||||
|
}
|
||||||
|
placeholder="请选择主题域"
|
||||||
|
onChange={(value) => {
|
||||||
|
setMetricOptions([...metricOptions]);
|
||||||
|
updateMetricList(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="params" label="指标">
|
||||||
|
<div className={styles.paramsSection}>
|
||||||
|
{metricOptions.map((filter: any) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.filterRow} key={filter.id}>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择指标,需先选择主题域"
|
||||||
|
options={(modelMetricList || []).map((metric) => ({
|
||||||
|
label: metric.name,
|
||||||
|
value: `${metric.id}`,
|
||||||
|
}))}
|
||||||
|
showSearch
|
||||||
|
className={styles.filterParamValueField}
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
((option?.label ?? '') as string)
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(input.toLowerCase())
|
||||||
|
}
|
||||||
|
allowClear
|
||||||
|
value={filter.metricId}
|
||||||
|
onChange={(value) => {
|
||||||
|
filter.metricId = value;
|
||||||
|
setMetricOptions([...metricOptions]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DeleteOutlined
|
||||||
|
onClick={() => {
|
||||||
|
setMetricOptions(metricOptions.filter((item) => item.id !== filter.id));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setMetricOptions([
|
||||||
|
...metricOptions,
|
||||||
|
{ id: uuid(), metricId: undefined, modelId: undefined },
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
新增指标
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{toolType === AgentToolTypeEnum.PLUGIN && (
|
||||||
|
<FormItem name="plugins" label="插件">
|
||||||
|
<Select
|
||||||
|
placeholder="请选择插件"
|
||||||
|
options={plugins.map((plugin) => ({ label: plugin.name, value: plugin.id }))}
|
||||||
|
showSearch
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
|
||||||
|
}
|
||||||
|
onChange={(value) => {
|
||||||
|
const plugin = plugins.find((item) => item.id === value);
|
||||||
|
if (plugin) {
|
||||||
|
form.setFieldsValue({ name: plugin.name });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
{toolType === AgentToolTypeEnum.RULE && (
|
||||||
|
<FormItem name="queryModes" label="查询模式">
|
||||||
|
<Select
|
||||||
|
placeholder="请选择查询模式"
|
||||||
|
options={QUERY_MODE_LIST}
|
||||||
|
showSearch
|
||||||
|
mode="multiple"
|
||||||
|
filterOption={(input, option) =>
|
||||||
|
((option?.label ?? '') as string).toLowerCase().includes(input.toLowerCase())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToolModal;
|
||||||
196
webapp/packages/supersonic-fe/src/pages/Agent/ToolsSection.tsx
Normal file
196
webapp/packages/supersonic-fe/src/pages/Agent/ToolsSection.tsx
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import { uuid } from '@/utils/utils';
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
ToolOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { Button, Empty, Popconfirm, Space, Switch, Tag } from 'antd';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import styles from './style.less';
|
||||||
|
import ToolModal from './ToolModal';
|
||||||
|
import { AgentToolType, AgentType, AGENT_TOOL_TYPE_LIST } from './type';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
currentAgent?: AgentType;
|
||||||
|
onSaveAgent: (agent: AgentType, noTip?: boolean) => Promise<void>;
|
||||||
|
onEditAgent: (agent?: AgentType) => void;
|
||||||
|
goBack: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ToolsSection: React.FC<Props> = ({ currentAgent, onSaveAgent, onEditAgent, goBack }) => {
|
||||||
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [editTool, setEditTool] = useState<AgentToolType>();
|
||||||
|
|
||||||
|
const agentConfig = currentAgent?.agentConfig ? JSON.parse(currentAgent.agentConfig as any) : {};
|
||||||
|
|
||||||
|
const saveAgent = async (agent: AgentType) => {
|
||||||
|
await onSaveAgent(agent);
|
||||||
|
setModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveTool = async (tool: AgentToolType) => {
|
||||||
|
const newAgentConfig = agentConfig || ({} as any);
|
||||||
|
if (!newAgentConfig.tools) {
|
||||||
|
newAgentConfig.tools = [];
|
||||||
|
}
|
||||||
|
if (tool.id) {
|
||||||
|
const index = newAgentConfig.tools.findIndex((item: AgentToolType) => item.id === tool.id);
|
||||||
|
newAgentConfig.tools[index] = tool;
|
||||||
|
} else {
|
||||||
|
newAgentConfig.tools.push({ ...tool, id: uuid() });
|
||||||
|
}
|
||||||
|
await saveAgent({
|
||||||
|
...currentAgent,
|
||||||
|
agentConfig: JSON.stringify(newAgentConfig) as any,
|
||||||
|
});
|
||||||
|
setModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteTool = async (tool: AgentToolType) => {
|
||||||
|
const newAgentConfig = agentConfig || ({} as any);
|
||||||
|
if (!newAgentConfig.tools) {
|
||||||
|
newAgentConfig.tools = [];
|
||||||
|
}
|
||||||
|
newAgentConfig.tools = newAgentConfig.tools.filter(
|
||||||
|
(item: AgentToolType) => item.id !== tool.id,
|
||||||
|
);
|
||||||
|
await saveAgent({
|
||||||
|
...currentAgent,
|
||||||
|
agentConfig: JSON.stringify(newAgentConfig) as any,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.toolsSection}>
|
||||||
|
<div className={styles.toolsSectionTitleBar}>
|
||||||
|
<ArrowLeftOutlined className={styles.backIcon} onClick={goBack} />
|
||||||
|
<div className={styles.agentTitle}>{currentAgent?.name}</div>
|
||||||
|
<div className={styles.toggleStatus}>
|
||||||
|
{currentAgent?.status === 0 ? '已禁用' : <span className={styles.online}>已启用</span>}
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
size="small"
|
||||||
|
defaultChecked={currentAgent?.status === 1}
|
||||||
|
onChange={(value) => {
|
||||||
|
onSaveAgent({ ...currentAgent, status: value ? 1 : 0 }, true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.basicInfo}>
|
||||||
|
<div className={styles.basicInfoTitle}>
|
||||||
|
基本信息
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
onEditAgent(currentAgent);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
修改基本信息
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.infoContent}>
|
||||||
|
<div className={styles.infoItem}>
|
||||||
|
示例问题:
|
||||||
|
<Space>
|
||||||
|
{currentAgent?.examples?.map((item) => (
|
||||||
|
<Tag key={item}>{item}</Tag>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<div className={styles.infoItem}>描述:{currentAgent?.description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.toolSection}>
|
||||||
|
<div className={styles.toolSectionTitleBar}>
|
||||||
|
<div className={styles.toolSectionTitle}>工具</div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setEditTool(undefined);
|
||||||
|
setModalVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined /> 新增工具
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{agentConfig?.tools && agentConfig?.tools?.length > 0 ? (
|
||||||
|
<div className={styles.toolsContent}>
|
||||||
|
{agentConfig.tools.map((tool: AgentToolType) => {
|
||||||
|
const toolType = AGENT_TOOL_TYPE_LIST.find((item) => item.value === tool.type)?.label;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.toolItem}
|
||||||
|
key={tool.id}
|
||||||
|
onClick={() => {
|
||||||
|
setEditTool(tool);
|
||||||
|
setModalVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ToolOutlined className={styles.toolIcon} />
|
||||||
|
<div className={styles.toolContent}>
|
||||||
|
<div className={styles.toolTopSection}>
|
||||||
|
<div className={styles.toolType}>{tool.name || toolType}</div>
|
||||||
|
<div className={styles.toolOperateIcons}>
|
||||||
|
<EditOutlined
|
||||||
|
className={styles.toolOperateIcon}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setEditTool(tool);
|
||||||
|
setModalVisible(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Popconfirm
|
||||||
|
title="确定删除吗?"
|
||||||
|
onCancel={(e) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
}}
|
||||||
|
onConfirm={(e) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
onDeleteTool(tool);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteOutlined
|
||||||
|
className={styles.toolOperateIcon}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.toolDesc} title={toolType}>
|
||||||
|
{toolType}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.emptyHolder}>
|
||||||
|
<Empty description={`【${currentAgent?.name}】暂无工具,请新增工具`} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{modalVisible && (
|
||||||
|
<ToolModal
|
||||||
|
editTool={editTool}
|
||||||
|
onSaveTool={onSaveTool}
|
||||||
|
onCancel={() => {
|
||||||
|
setModalVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToolsSection;
|
||||||
94
webapp/packages/supersonic-fe/src/pages/Agent/index.tsx
Normal file
94
webapp/packages/supersonic-fe/src/pages/Agent/index.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { message } from 'antd';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import AgentsSection from './AgentsSection';
|
||||||
|
import AgentModal from './AgentModal';
|
||||||
|
import { deleteAgent, getAgentList, saveAgent } from './service';
|
||||||
|
import styles from './style.less';
|
||||||
|
import ToolsSection from './ToolsSection';
|
||||||
|
import { AgentType } from './type';
|
||||||
|
|
||||||
|
const Agent = () => {
|
||||||
|
const [agents, setAgents] = useState<AgentType[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [currentAgent, setCurrentAgent] = useState<AgentType>();
|
||||||
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
const [editAgent, setEditAgent] = useState<AgentType>();
|
||||||
|
|
||||||
|
const updateData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await getAgentList();
|
||||||
|
setLoading(false);
|
||||||
|
setAgents(res.data || []);
|
||||||
|
if (!res.data?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentAgent) {
|
||||||
|
const agent = res.data.find((item) => item.id === currentAgent.id);
|
||||||
|
if (agent) {
|
||||||
|
setCurrentAgent(agent);
|
||||||
|
} else {
|
||||||
|
setCurrentAgent(res.data[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSaveAgent = async (agent: AgentType, noTip?: boolean) => {
|
||||||
|
await saveAgent(agent);
|
||||||
|
if (!noTip) {
|
||||||
|
message.success('保存成功');
|
||||||
|
}
|
||||||
|
setModalVisible(false);
|
||||||
|
updateData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteAgent = async (id: number) => {
|
||||||
|
await deleteAgent(id);
|
||||||
|
message.success('删除成功');
|
||||||
|
updateData();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEditAgent = (agent?: AgentType) => {
|
||||||
|
setEditAgent(agent);
|
||||||
|
setModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.agent}>
|
||||||
|
{!currentAgent ? (
|
||||||
|
<AgentsSection
|
||||||
|
agents={agents}
|
||||||
|
currentAgent={currentAgent}
|
||||||
|
loading={loading}
|
||||||
|
onSelectAgent={setCurrentAgent}
|
||||||
|
onEditAgent={onEditAgent}
|
||||||
|
onDeleteAgent={onDeleteAgent}
|
||||||
|
onSaveAgent={onSaveAgent}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ToolsSection
|
||||||
|
currentAgent={currentAgent}
|
||||||
|
onSaveAgent={onSaveAgent}
|
||||||
|
onEditAgent={onEditAgent}
|
||||||
|
goBack={() => {
|
||||||
|
setCurrentAgent(undefined);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{modalVisible && (
|
||||||
|
<AgentModal
|
||||||
|
editAgent={editAgent}
|
||||||
|
onSaveAgent={onSaveAgent}
|
||||||
|
onCancel={() => {
|
||||||
|
setModalVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Agent;
|
||||||
36
webapp/packages/supersonic-fe/src/pages/Agent/service.ts
Normal file
36
webapp/packages/supersonic-fe/src/pages/Agent/service.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { request } from "umi";
|
||||||
|
import { AgentType, MetricType, ModelType } from "./type";
|
||||||
|
|
||||||
|
export function getAgentList() {
|
||||||
|
return request<Result<AgentType[]>>('/api/chat/agent/getAgentList');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveAgent(agent: AgentType) {
|
||||||
|
return request<Result<any>>('/api/chat/agent', {
|
||||||
|
method: agent?.id ? 'PUT' : 'POST',
|
||||||
|
data: {...agent, status: agent.status !== undefined ? agent.status : 1},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteAgent(id: number) {
|
||||||
|
return request<Result<any>>(`/api/chat/agent/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getModelList() {
|
||||||
|
return request<Result<ModelType[]>>('/api/chat/conf/modelList', {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMetricList(modelId: number) {
|
||||||
|
return request<Result<{list: MetricType[]}>>('/api/semantic/metric/queryMetric', {
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
modelIds: [modelId],
|
||||||
|
current: 1,
|
||||||
|
pageSize: 2000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
292
webapp/packages/supersonic-fe/src/pages/Agent/style.less
Normal file
292
webapp/packages/supersonic-fe/src/pages/Agent/style.less
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
.agent {
|
||||||
|
// background: #fff;
|
||||||
|
height: calc(100vh - 48px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agentsSection {
|
||||||
|
padding: 20px 40px;
|
||||||
|
background: #fff;
|
||||||
|
height: calc(100vh - 48px);
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin-top: 20px;
|
||||||
|
.searchBar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
.searchControl {
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agentsContainer {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.agentItem {
|
||||||
|
display: flex;
|
||||||
|
width: 290px;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--chat-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.agentActive {
|
||||||
|
border-color: var(--chat-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agentIcon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--chat-blue);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agentContent {
|
||||||
|
margin-left: 12px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.agentNameBar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.agentName {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operateIcons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 12px;
|
||||||
|
.operateIcon {
|
||||||
|
color: var(--text-color-fourth);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--chat-blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottomBar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 210px;
|
||||||
|
|
||||||
|
.agentDescription {
|
||||||
|
width: 120px;
|
||||||
|
margin-right: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--text-color-third)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleStatus {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.online {
|
||||||
|
color: var(--chat-blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolsSection {
|
||||||
|
.toolsSectionTitleBar {
|
||||||
|
padding: 20px 30px;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 20px;
|
||||||
|
|
||||||
|
.backIcon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--chat-blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agentTitle {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.paramsSection {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 12px;
|
||||||
|
.filterRow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 12px;
|
||||||
|
|
||||||
|
.filterParamName {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterParamValueField {
|
||||||
|
width: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.questionExample {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.basicInfo {
|
||||||
|
margin: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.basicInfoTitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
column-gap: 30px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 14px 20px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoContent {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolSection {
|
||||||
|
margin: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.toolSectionTitleBar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
column-gap: 30px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
|
||||||
|
.toolSectionTitle {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyHolder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolsContent {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px 20px 30px;
|
||||||
|
|
||||||
|
.toolItem {
|
||||||
|
width: 300px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.toolIcon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--chat-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolContent {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.toolTopSection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.toolType {
|
||||||
|
flex: 1;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolOperateIcons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 10px;
|
||||||
|
|
||||||
|
.toolOperateIcon {
|
||||||
|
color: var(--text-color-third);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--chat-blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolDesc {
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 220px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-color-third);
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
webapp/packages/supersonic-fe/src/pages/Agent/type.ts
Normal file
119
webapp/packages/supersonic-fe/src/pages/Agent/type.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
export type MetricOptionType = {
|
||||||
|
id: string;
|
||||||
|
metricId?: number;
|
||||||
|
modelId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AgentToolTypeEnum {
|
||||||
|
RULE = 'RULE',
|
||||||
|
DSL = 'DSL',
|
||||||
|
PLUGIN = 'PLUGIN',
|
||||||
|
INTERPRET = 'INTERPRET'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum QueryModeEnum {
|
||||||
|
ENTITY_DETAIL = 'ENTITY_DETAIL',
|
||||||
|
ENTITY_LIST_FILTER = 'ENTITY_LIST_FILTER',
|
||||||
|
ENTITY_ID = 'ENTITY_ID',
|
||||||
|
METRIC_ENTITY = 'METRIC_ENTITY',
|
||||||
|
METRIC_FILTER = 'METRIC_FILTER',
|
||||||
|
METRIC_GROUPBY = 'METRIC_GROUPBY',
|
||||||
|
METRIC_MODEL = 'METRIC_MODEL',
|
||||||
|
METRIC_ORDERBY = 'METRIC_ORDERBY'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AGENT_TOOL_TYPE_LIST = [
|
||||||
|
{
|
||||||
|
label: '规则',
|
||||||
|
value: AgentToolTypeEnum.RULE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'LLM语义解析',
|
||||||
|
value: AgentToolTypeEnum.DSL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '指标解读',
|
||||||
|
value: AgentToolTypeEnum.INTERPRET
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '插件',
|
||||||
|
value: AgentToolTypeEnum.PLUGIN
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const QUERY_MODE_LIST = [
|
||||||
|
{
|
||||||
|
label: '实体明细(查询维度信息)',
|
||||||
|
value: QueryModeEnum.ENTITY_DETAIL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '实体圈选',
|
||||||
|
value: QueryModeEnum.ENTITY_LIST_FILTER
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '实体查询(按ID查询)',
|
||||||
|
value: QueryModeEnum.ENTITY_ID
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '指标查询(带实体)',
|
||||||
|
value: QueryModeEnum.METRIC_ENTITY
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '指标查询(带条件)',
|
||||||
|
value: QueryModeEnum.METRIC_FILTER
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '指标查询(按维度分组)',
|
||||||
|
value: QueryModeEnum.METRIC_GROUPBY
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '指标查询(不带条件)',
|
||||||
|
value: QueryModeEnum.METRIC_MODEL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '按指标排序',
|
||||||
|
value: QueryModeEnum.METRIC_ORDERBY
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export type AgentToolType = {
|
||||||
|
id?: string;
|
||||||
|
type: AgentToolTypeEnum;
|
||||||
|
name: string;
|
||||||
|
queryModes?: QueryModeEnum[];
|
||||||
|
plugins?: number[];
|
||||||
|
metricOptions?: MetricOptionType[];
|
||||||
|
exampleQuestions?: string[];
|
||||||
|
modelIds?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentConfigType = {
|
||||||
|
tools: AgentToolType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AgentType = {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
createdBy?: string;
|
||||||
|
updatedBy?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
examples?: string[];
|
||||||
|
status?: 0 | 1;
|
||||||
|
enableSearch?: 0 | 1;
|
||||||
|
agentConfig?: AgentConfigType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ModelType = {
|
||||||
|
id: number | string;
|
||||||
|
parentId: number;
|
||||||
|
name: string;
|
||||||
|
bizName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MetricType = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
bizName: string;
|
||||||
|
};
|
||||||
@@ -8,24 +8,26 @@ import type { ForwardRefRenderFunction } from 'react';
|
|||||||
import { searchRecommend } from 'supersonic-chat-sdk';
|
import { searchRecommend } from 'supersonic-chat-sdk';
|
||||||
import { SemanticTypeEnum, SEMANTIC_TYPE_MAP } from '../constants';
|
import { SemanticTypeEnum, SEMANTIC_TYPE_MAP } from '../constants';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
import { PLACE_HOLDER } from '../constants';
|
import { DefaultEntityType, AgentType, ModelType } from '../type';
|
||||||
import { DefaultEntityType, ModelType } from '../type';
|
|
||||||
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
|
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
inputMsg: string;
|
inputMsg: string;
|
||||||
chatId?: number;
|
chatId?: number;
|
||||||
currentModel?: ModelType;
|
currentModel?: ModelType;
|
||||||
|
currentAgent?: AgentType;
|
||||||
defaultEntity?: DefaultEntityType;
|
defaultEntity?: DefaultEntityType;
|
||||||
isCopilotMode?: boolean;
|
isCopilotMode?: boolean;
|
||||||
copilotFullscreen?: boolean;
|
copilotFullscreen?: boolean;
|
||||||
models: ModelType[];
|
models: ModelType[];
|
||||||
|
agentList: AgentType[];
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
onToggleCollapseBtn: () => void;
|
onToggleCollapseBtn: () => void;
|
||||||
onInputMsgChange: (value: string) => void;
|
onInputMsgChange: (value: string) => void;
|
||||||
onSendMsg: (msg: string, modelId?: number) => void;
|
onSendMsg: (msg: string, modelId?: number) => void;
|
||||||
onAddConversation: () => void;
|
onAddConversation: () => void;
|
||||||
onCancelDefaultFilter: () => void;
|
onCancelDefaultFilter: () => void;
|
||||||
|
onSelectAgent: (agent: AgentType) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { OptGroup, Option } = Select;
|
const { OptGroup, Option } = Select;
|
||||||
@@ -45,8 +47,10 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
inputMsg,
|
inputMsg,
|
||||||
chatId,
|
chatId,
|
||||||
currentModel,
|
currentModel,
|
||||||
|
currentAgent,
|
||||||
defaultEntity,
|
defaultEntity,
|
||||||
models,
|
models,
|
||||||
|
agentList,
|
||||||
collapsed,
|
collapsed,
|
||||||
isCopilotMode,
|
isCopilotMode,
|
||||||
copilotFullscreen,
|
copilotFullscreen,
|
||||||
@@ -55,10 +59,11 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
onSendMsg,
|
onSendMsg,
|
||||||
onAddConversation,
|
onAddConversation,
|
||||||
onCancelDefaultFilter,
|
onCancelDefaultFilter,
|
||||||
|
onSelectAgent,
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const [modelOptions, setModelOptions] = useState<ModelType[]>([]);
|
const [modelOptions, setModelOptions] = useState<(ModelType | AgentType)[]>([]);
|
||||||
const [stepOptions, setStepOptions] = useState<Record<string, any[]>>({});
|
const [stepOptions, setStepOptions] = useState<Record<string, any[]>>({});
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [focused, setFocused] = useState(false);
|
const [focused, setFocused] = useState(false);
|
||||||
@@ -121,6 +126,9 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
const model = models.find((item) => msg.includes(`@${item.name}`));
|
const model = models.find((item) => msg.includes(`@${item.name}`));
|
||||||
msgValue = model ? msg.replace(`@${model.name}`, '') : msg;
|
msgValue = model ? msg.replace(`@${model.name}`, '') : msg;
|
||||||
modelId = model?.id;
|
modelId = model?.id;
|
||||||
|
} else if (msg?.[0] === '/') {
|
||||||
|
const agent = agentList.find((item) => msg.includes(`/${item.name}`));
|
||||||
|
msgValue = agent ? msg.replace(`/${agent.name}`, '') : msg;
|
||||||
}
|
}
|
||||||
return { msgValue, modelId };
|
return { msgValue, modelId };
|
||||||
};
|
};
|
||||||
@@ -163,9 +171,9 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
const [debounceGetWords] = useState<any>(debounceGetWordsFunc);
|
const [debounceGetWords] = useState<any>(debounceGetWordsFunc);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputMsg.length === 1 && inputMsg[0] === '@') {
|
if (inputMsg.length === 1 && (inputMsg[0] === '@' || inputMsg[0] === '/')) {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setModelOptions(models);
|
setModelOptions(inputMsg[0] === '/' ? agentList : models);
|
||||||
setStepOptions({});
|
setStepOptions({});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -173,10 +181,10 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
if (modelOptions.length > 0) {
|
if (modelOptions.length > 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setModelOptions([]);
|
setModelOptions([]);
|
||||||
}, 500);
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isSelect) {
|
if (!isSelect && currentAgent?.name !== '问知识') {
|
||||||
debounceGetWords(inputMsg, models, chatId, currentModel);
|
debounceGetWords(inputMsg, models, chatId, currentModel);
|
||||||
} else {
|
} else {
|
||||||
isSelect = false;
|
isSelect = false;
|
||||||
@@ -237,6 +245,12 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
isSelect = true;
|
isSelect = true;
|
||||||
if (modelOptions.length === 0) {
|
if (modelOptions.length === 0) {
|
||||||
sendMsg(value);
|
sendMsg(value);
|
||||||
|
} else {
|
||||||
|
const agent = agentList.find((item) => value.includes(item.name));
|
||||||
|
if (agent) {
|
||||||
|
onSelectAgent(agent);
|
||||||
|
onInputMsgChange('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -249,29 +263,14 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
[styles.defaultCopilotMode]: isCopilotMode && !copilotFullscreen,
|
[styles.defaultCopilotMode]: isCopilotMode && !copilotFullscreen,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
const restrictNode = currentModel && !isMobile && (
|
||||||
<div className={chatFooterClass}>
|
|
||||||
<div className={styles.composer}>
|
|
||||||
<div className={styles.collapseBtn} onClick={onToggleCollapseBtn}>
|
|
||||||
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
||||||
</div>
|
|
||||||
<Tooltip title="新建对话">
|
|
||||||
<IconFont
|
|
||||||
type="icon-icon-add-conversation-line"
|
|
||||||
className={styles.addConversation}
|
|
||||||
onClick={onAddConversation}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<div className={styles.composerInputWrapper}>
|
|
||||||
{currentModel && (
|
|
||||||
<div className={styles.currentModel}>
|
<div className={styles.currentModel}>
|
||||||
<div className={styles.currentModelName}>
|
<div className={styles.currentModelName}>
|
||||||
输入联想与问题回复将限定于:“
|
输入联想与问题回复将限定于:“
|
||||||
<span className={styles.quoteText}>
|
<span className={styles.quoteText}>
|
||||||
主题域【{currentModel.name}】
|
{!defaultEntity && <>主题域【{currentModel.name}】</>}
|
||||||
{defaultEntity && (
|
{defaultEntity && (
|
||||||
<>
|
<>
|
||||||
<span>,</span>
|
|
||||||
<span>{`${currentModel.name.slice(0, currentModel.name.length - 1)}【`}</span>
|
<span>{`${currentModel.name.slice(0, currentModel.name.length - 1)}【`}</span>
|
||||||
<span className={styles.entityName} title={defaultEntity.entityName}>
|
<span className={styles.entityName} title={defaultEntity.entityName}>
|
||||||
{defaultEntity.entityName}
|
{defaultEntity.entityName}
|
||||||
@@ -286,53 +285,21 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
取消限定
|
取消限定
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
<AutoComplete
|
|
||||||
className={styles.composerInput}
|
const modelOptionNodes = modelOptions.map((model) => {
|
||||||
placeholder={
|
|
||||||
currentModel
|
|
||||||
? `请输入【${currentModel.name}】主题的问题,可使用@切换到其他主题`
|
|
||||||
: PLACE_HOLDER
|
|
||||||
}
|
|
||||||
value={inputMsg}
|
|
||||||
onChange={onInputMsgChange}
|
|
||||||
onSelect={onSelect}
|
|
||||||
autoFocus={!isMobile}
|
|
||||||
backfill
|
|
||||||
ref={inputRef}
|
|
||||||
id="chatInput"
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if ((e.code === 'Enter' || e.code === 'NumpadEnter') && !isSelect) {
|
|
||||||
const chatInputEl: any = document.getElementById('chatInput');
|
|
||||||
sendMsg(chatInputEl.value);
|
|
||||||
setOpen(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
setFocused(true);
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
setFocused(false);
|
|
||||||
}}
|
|
||||||
dropdownClassName={autoCompleteDropdownClass}
|
|
||||||
listHeight={500}
|
|
||||||
allowClear
|
|
||||||
open={open}
|
|
||||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
|
||||||
>
|
|
||||||
{modelOptions.length > 0
|
|
||||||
? modelOptions.map((model) => {
|
|
||||||
return (
|
return (
|
||||||
<Option
|
<Option
|
||||||
key={model.id}
|
key={model.id}
|
||||||
value={`@${model.name} `}
|
value={inputMsg[0] === '/' ? `/${model.name} ` : `@${model.name} `}
|
||||||
className={styles.searchOption}
|
className={styles.searchOption}
|
||||||
>
|
>
|
||||||
{model.name}
|
{model.name}
|
||||||
</Option>
|
</Option>
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
: Object.keys(stepOptions).map((key) => {
|
|
||||||
|
const associateOptionNodes = Object.keys(stepOptions).map((key) => {
|
||||||
return (
|
return (
|
||||||
<OptGroup key={key} label={key}>
|
<OptGroup key={key} label={key}>
|
||||||
{stepOptions[key].map((option) => {
|
{stepOptions[key].map((option) => {
|
||||||
@@ -343,12 +310,13 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
if (inputMsg[0] === '@') {
|
if (inputMsg[0] === '@') {
|
||||||
const model = models.find((item) => inputMsg.includes(item.name));
|
const model = models.find((item) => inputMsg.includes(item.name));
|
||||||
optionValue = model ? `@${model.name} ${option.recommend}` : optionValue;
|
optionValue = model ? `@${model.name} ${option.recommend}` : optionValue;
|
||||||
|
} else if (inputMsg[0] === '/') {
|
||||||
|
const agent = agentList.find((item) => inputMsg.includes(item.name));
|
||||||
|
optionValue = agent ? `/${agent.name} ${option.recommend}` : optionValue;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Option
|
<Option
|
||||||
key={`${option.recommend}${
|
key={`${option.recommend}${option.modelName ? `_${option.modelName}` : ''}`}
|
||||||
option.modelName ? `_${option.modelName}` : ''
|
|
||||||
}`}
|
|
||||||
value={optionValue}
|
value={optionValue}
|
||||||
className={styles.searchOption}
|
className={styles.searchOption}
|
||||||
>
|
>
|
||||||
@@ -377,7 +345,70 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
|||||||
})}
|
})}
|
||||||
</OptGroup>
|
</OptGroup>
|
||||||
);
|
);
|
||||||
})}
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={chatFooterClass}>
|
||||||
|
<div className={styles.composer}>
|
||||||
|
{!isMobile && (
|
||||||
|
<div className={styles.collapseBtn} onClick={onToggleCollapseBtn}>
|
||||||
|
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Tooltip title="新建对话">
|
||||||
|
<IconFont
|
||||||
|
type="icon-icon-add-conversation-line"
|
||||||
|
className={styles.addConversation}
|
||||||
|
onClick={onAddConversation}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<div className={styles.composerInputWrapper}>
|
||||||
|
{/* {restrictNode}
|
||||||
|
{currentAgentNode} */}
|
||||||
|
<AutoComplete
|
||||||
|
className={styles.composerInput}
|
||||||
|
placeholder={
|
||||||
|
currentAgent?.name
|
||||||
|
? `智能助理【${currentAgent?.name}】将与您对话,可输入“/”切换助理`
|
||||||
|
: '请输入您的问题'
|
||||||
|
}
|
||||||
|
value={inputMsg}
|
||||||
|
onChange={onInputMsgChange}
|
||||||
|
onSelect={onSelect}
|
||||||
|
autoFocus={!isMobile}
|
||||||
|
backfill
|
||||||
|
ref={inputRef}
|
||||||
|
id="chatInput"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.code === 'Enter' || e.code === 'NumpadEnter') {
|
||||||
|
{
|
||||||
|
const chatInputEl: any = document.getElementById('chatInput');
|
||||||
|
if (!isSelect) {
|
||||||
|
sendMsg(chatInputEl.value);
|
||||||
|
setOpen(false);
|
||||||
|
} else {
|
||||||
|
const agent = agentList.find((item) => chatInputEl.value.includes(item.name));
|
||||||
|
if (agent) {
|
||||||
|
onSelectAgent(agent);
|
||||||
|
onInputMsgChange('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onFocus={() => {
|
||||||
|
setFocused(true);
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setFocused(false);
|
||||||
|
}}
|
||||||
|
dropdownClassName={autoCompleteDropdownClass}
|
||||||
|
listHeight={500}
|
||||||
|
allowClear
|
||||||
|
open={open}
|
||||||
|
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||||
|
>
|
||||||
|
{modelOptions.length > 0 ? modelOptionNodes : associateOptionNodes}
|
||||||
</AutoComplete>
|
</AutoComplete>
|
||||||
<div
|
<div
|
||||||
className={classNames(styles.sendBtn, {
|
className={classNames(styles.sendBtn, {
|
||||||
|
|||||||
@@ -90,7 +90,12 @@ const Conversation: ForwardRefRenderFunction<any, Props> = (
|
|||||||
defaultEntityFilter?.entityName && window.location.pathname.includes('detail')
|
defaultEntityFilter?.entityName && window.location.pathname.includes('detail')
|
||||||
? defaultEntityFilter.entityName
|
? defaultEntityFilter.entityName
|
||||||
: defaultModelName;
|
: defaultModelName;
|
||||||
onAddConversation({ name: conversationName, type: 'CUSTOMIZE' });
|
onAddConversation({
|
||||||
|
name: conversationName,
|
||||||
|
type: 'CUSTOMIZE',
|
||||||
|
modelId: defaultEntityFilter?.modelId,
|
||||||
|
entityId: defaultEntityFilter?.entityId,
|
||||||
|
});
|
||||||
onNewConversationTriggered?.();
|
onNewConversationTriggered?.();
|
||||||
}
|
}
|
||||||
}, [triggerNewConversation]);
|
}, [triggerNewConversation]);
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ import { memo, useCallback, useEffect, useState } from 'react';
|
|||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { ChatItem } from 'supersonic-chat-sdk';
|
import { ChatItem } from 'supersonic-chat-sdk';
|
||||||
import type { MsgDataType } from 'supersonic-chat-sdk';
|
import type { MsgDataType } from 'supersonic-chat-sdk';
|
||||||
import { MessageItem, MessageTypeEnum } from './type';
|
import { AgentType, MessageItem, MessageTypeEnum } from './type';
|
||||||
import Plugin from './components/Plugin';
|
import Plugin from './components/Plugin';
|
||||||
import { updateMessageContainerScroll } from '@/utils/utils';
|
import { updateMessageContainerScroll } from '@/utils/utils';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
|
import { MODEL_MODEL_ENTITY_ID_FILTER_MAP } from './constants';
|
||||||
|
import AgentList from './components/AgentList';
|
||||||
|
import RecommendQuestions from './components/RecommendQuestions';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -15,6 +18,7 @@ type Props = {
|
|||||||
isMobileMode?: boolean;
|
isMobileMode?: boolean;
|
||||||
conversationCollapsed: boolean;
|
conversationCollapsed: boolean;
|
||||||
copilotFullscreen?: boolean;
|
copilotFullscreen?: boolean;
|
||||||
|
agentList: AgentType[];
|
||||||
onClickMessageContainer: () => void;
|
onClickMessageContainer: () => void;
|
||||||
onMsgDataLoaded: (
|
onMsgDataLoaded: (
|
||||||
data: MsgDataType,
|
data: MsgDataType,
|
||||||
@@ -24,6 +28,8 @@ type Props = {
|
|||||||
) => void;
|
) => void;
|
||||||
onCheckMore: (data: MsgDataType) => void;
|
onCheckMore: (data: MsgDataType) => void;
|
||||||
onApplyAuth: (model: string) => void;
|
onApplyAuth: (model: string) => void;
|
||||||
|
onSendMsg: (value: string) => void;
|
||||||
|
onSelectAgent: (agent: AgentType) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MessageContainer: React.FC<Props> = ({
|
const MessageContainer: React.FC<Props> = ({
|
||||||
@@ -33,10 +39,12 @@ const MessageContainer: React.FC<Props> = ({
|
|||||||
isMobileMode,
|
isMobileMode,
|
||||||
conversationCollapsed,
|
conversationCollapsed,
|
||||||
copilotFullscreen,
|
copilotFullscreen,
|
||||||
|
agentList,
|
||||||
onClickMessageContainer,
|
onClickMessageContainer,
|
||||||
onMsgDataLoaded,
|
onMsgDataLoaded,
|
||||||
onCheckMore,
|
onCheckMore,
|
||||||
onApplyAuth,
|
onSendMsg,
|
||||||
|
onSelectAgent,
|
||||||
}) => {
|
}) => {
|
||||||
const [triggerResize, setTriggerResize] = useState(false);
|
const [triggerResize, setTriggerResize] = useState(false);
|
||||||
|
|
||||||
@@ -97,6 +105,7 @@ const MessageContainer: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
...MODEL_MODEL_ENTITY_ID_FILTER_MAP[modelId],
|
||||||
value: entityId,
|
value: entityId,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -109,6 +118,7 @@ const MessageContainer: React.FC<Props> = ({
|
|||||||
const {
|
const {
|
||||||
id: msgId,
|
id: msgId,
|
||||||
modelId,
|
modelId,
|
||||||
|
agentId,
|
||||||
entityId,
|
entityId,
|
||||||
type,
|
type,
|
||||||
msg,
|
msg,
|
||||||
@@ -125,6 +135,17 @@ const MessageContainer: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div key={msgId} id={`${msgId}`} className={styles.messageItem}>
|
<div key={msgId} id={`${msgId}`} className={styles.messageItem}>
|
||||||
{type === MessageTypeEnum.TEXT && <Text position="left" data={msg} />}
|
{type === MessageTypeEnum.TEXT && <Text position="left" data={msg} />}
|
||||||
|
{type === MessageTypeEnum.RECOMMEND_QUESTIONS && (
|
||||||
|
<RecommendQuestions onSelectQuestion={onSendMsg} />
|
||||||
|
)}
|
||||||
|
{type === MessageTypeEnum.AGENT_LIST && (
|
||||||
|
<AgentList
|
||||||
|
currentAgentName={msg!}
|
||||||
|
data={agentList}
|
||||||
|
copilotFullscreen={copilotFullscreen}
|
||||||
|
onSelectAgent={onSelectAgent}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{type === MessageTypeEnum.QUESTION && (
|
{type === MessageTypeEnum.QUESTION && (
|
||||||
<>
|
<>
|
||||||
<Text position="right" data={msg} />
|
<Text position="right" data={msg} />
|
||||||
@@ -134,6 +155,7 @@ const MessageContainer: React.FC<Props> = ({
|
|||||||
msgData={msgData}
|
msgData={msgData}
|
||||||
conversationId={chatId}
|
conversationId={chatId}
|
||||||
modelId={modelId}
|
modelId={modelId}
|
||||||
|
agentId={agentId}
|
||||||
filter={getFilters(modelId, entityId)}
|
filter={getFilters(modelId, entityId)}
|
||||||
isLastMessage={index === messageList.length - 1}
|
isLastMessage={index === messageList.length - 1}
|
||||||
isMobileMode={isMobileMode}
|
isMobileMode={isMobileMode}
|
||||||
@@ -150,6 +172,7 @@ const MessageContainer: React.FC<Props> = ({
|
|||||||
msg={msgValue || msg || ''}
|
msg={msgValue || msg || ''}
|
||||||
conversationId={chatId}
|
conversationId={chatId}
|
||||||
modelId={modelId}
|
modelId={modelId}
|
||||||
|
agentId={agentId}
|
||||||
filter={getFilters(modelId, entityId)}
|
filter={getFilters(modelId, entityId)}
|
||||||
isLastMessage={index === messageList.length - 1}
|
isLastMessage={index === messageList.length - 1}
|
||||||
isMobileMode={isMobileMode}
|
isMobileMode={isMobileMode}
|
||||||
@@ -192,7 +215,8 @@ function areEqual(prevProps: Props, nextProps: Props) {
|
|||||||
prevProps.id === nextProps.id &&
|
prevProps.id === nextProps.id &&
|
||||||
isEqual(prevProps.messageList, nextProps.messageList) &&
|
isEqual(prevProps.messageList, nextProps.messageList) &&
|
||||||
prevProps.conversationCollapsed === nextProps.conversationCollapsed &&
|
prevProps.conversationCollapsed === nextProps.conversationCollapsed &&
|
||||||
prevProps.copilotFullscreen === nextProps.copilotFullscreen
|
prevProps.copilotFullscreen === nextProps.copilotFullscreen &&
|
||||||
|
prevProps.agentList === nextProps.agentList
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import LeftAvatar from '../CopilotAvatar';
|
||||||
|
import Message from '../Message';
|
||||||
|
import styles from './style.less';
|
||||||
|
import { AgentType } from '../../type';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
currentAgentName: string;
|
||||||
|
data: AgentType[];
|
||||||
|
copilotFullscreen?: boolean;
|
||||||
|
onSelectAgent: (agent: AgentType) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AgentList: React.FC<Props> = ({
|
||||||
|
currentAgentName,
|
||||||
|
data,
|
||||||
|
copilotFullscreen,
|
||||||
|
onSelectAgent,
|
||||||
|
}) => {
|
||||||
|
const agentClass = classNames(styles.agent, {
|
||||||
|
[styles.fullscreen]: copilotFullscreen,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className={styles.agentList}>
|
||||||
|
<LeftAvatar />
|
||||||
|
<Message position="left" bubbleClassName={styles.agentListMsg}>
|
||||||
|
<div className={styles.title}>
|
||||||
|
您好,智能助理【{currentAgentName}
|
||||||
|
】将与您对话,点击以下卡片或者输入“/”可切换助理:
|
||||||
|
</div>
|
||||||
|
<div className={styles.content}>
|
||||||
|
{data.map((agent) => (
|
||||||
|
<div
|
||||||
|
key={agent.id}
|
||||||
|
className={agentClass}
|
||||||
|
onClick={() => {
|
||||||
|
onSelectAgent(agent);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={styles.topBar}>
|
||||||
|
<div className={styles.agentName}>{agent.name}</div>
|
||||||
|
<div className={styles.tip}>您可以这样问:</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.examples}>
|
||||||
|
{agent.examples?.length > 0 ? (
|
||||||
|
agent.examples.map((example) => (
|
||||||
|
<div key={example} className={styles.example}>
|
||||||
|
“{example}”
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className={styles.example}>{agent.description}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Message>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AgentList;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
.agentList {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
.agentListMsg {
|
||||||
|
padding: 12px 20px 20px !important;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
column-gap: 14px;
|
||||||
|
row-gap: 14px;
|
||||||
|
|
||||||
|
.agent {
|
||||||
|
flex: 0 0 calc(50% - 7px);
|
||||||
|
padding: 10px 14px 20px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
border-radius: 17px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.topBar {
|
||||||
|
.agentName {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.examples {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
row-gap: 8px;
|
||||||
|
|
||||||
|
.example {
|
||||||
|
padding: 4px 12px;
|
||||||
|
background-color: #ededed;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fullscreen {
|
||||||
|
flex: none;
|
||||||
|
width: 280px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,9 @@ import {
|
|||||||
ModelType,
|
ModelType,
|
||||||
MessageItem,
|
MessageItem,
|
||||||
MessageTypeEnum,
|
MessageTypeEnum,
|
||||||
|
AgentType,
|
||||||
} from './type';
|
} from './type';
|
||||||
import { getModelList } from './service';
|
import { getModelList, queryAgentList } from './service';
|
||||||
import { useThrottleFn } from 'ahooks';
|
import { useThrottleFn } from 'ahooks';
|
||||||
import Conversation from './Conversation';
|
import Conversation from './Conversation';
|
||||||
import ChatFooter from './ChatFooter';
|
import ChatFooter from './ChatFooter';
|
||||||
@@ -64,6 +65,8 @@ const Chat: React.FC<Props> = ({
|
|||||||
const [applyAuthVisible, setApplyAuthVisible] = useState(false);
|
const [applyAuthVisible, setApplyAuthVisible] = useState(false);
|
||||||
const [applyAuthModel, setApplyAuthModel] = useState('');
|
const [applyAuthModel, setApplyAuthModel] = useState('');
|
||||||
const [initialModelName, setInitialModelName] = useState('');
|
const [initialModelName, setInitialModelName] = useState('');
|
||||||
|
const [agentList, setAgentList] = useState<AgentType[]>([]);
|
||||||
|
const [currentAgent, setCurrentAgent] = useState<AgentType>();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { modelName } = (location as any).query;
|
const { modelName } = (location as any).query;
|
||||||
@@ -71,9 +74,19 @@ const Chat: React.FC<Props> = ({
|
|||||||
const conversationRef = useRef<any>();
|
const conversationRef = useRef<any>();
|
||||||
const chatFooterRef = useRef<any>();
|
const chatFooterRef = useRef<any>();
|
||||||
|
|
||||||
|
const initAgentList = async () => {
|
||||||
|
const res = await queryAgentList();
|
||||||
|
const agentListValue = (res.data || []).filter((item) => item.status === 1);
|
||||||
|
setAgentList(agentListValue);
|
||||||
|
if (agentListValue.length > 0) {
|
||||||
|
setCurrentAgent(agentListValue[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setChatSdkToken(localStorage.getItem(AUTH_TOKEN_KEY) || '');
|
setChatSdkToken(localStorage.getItem(AUTH_TOKEN_KEY) || '');
|
||||||
initModels();
|
initModels();
|
||||||
|
initAgentList();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -102,7 +115,13 @@ const Chat: React.FC<Props> = ({
|
|||||||
if (initMsg) {
|
if (initMsg) {
|
||||||
inputFocus();
|
inputFocus();
|
||||||
if (initMsg === 'CUSTOMIZE' && copilotSendMsg) {
|
if (initMsg === 'CUSTOMIZE' && copilotSendMsg) {
|
||||||
onSendMsg(copilotSendMsg, [], modelId, entityId);
|
onSendMsg(
|
||||||
|
copilotSendMsg,
|
||||||
|
[],
|
||||||
|
modelId,
|
||||||
|
entityId,
|
||||||
|
agentList.find((item) => item.name === '做分析'),
|
||||||
|
);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'globalState/setCopilotSendMsg',
|
type: 'globalState/setCopilotSendMsg',
|
||||||
payload: '',
|
payload: '',
|
||||||
@@ -143,16 +162,9 @@ const Chat: React.FC<Props> = ({
|
|||||||
setMessageList([
|
setMessageList([
|
||||||
{
|
{
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
type: MessageTypeEnum.TEXT,
|
type: MessageTypeEnum.RECOMMEND_QUESTIONS,
|
||||||
msg: defaultModelName
|
// type: MessageTypeEnum.AGENT_LIST,
|
||||||
? `您好,请输入关于${
|
// msg: currentAgent?.name || '查信息',
|
||||||
defaultEntityFilter?.entityName
|
|
||||||
? `${defaultModelName?.slice(0, defaultModelName?.length - 1)}【${
|
|
||||||
defaultEntityFilter?.entityName
|
|
||||||
}】`
|
|
||||||
: `【${defaultModelName}】`
|
|
||||||
}的问题`
|
|
||||||
: '您好,请问有什么我能帮您吗?',
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
@@ -161,7 +173,6 @@ const Chat: React.FC<Props> = ({
|
|||||||
return list.map((item: HistoryMsgItemType) => ({
|
return list.map((item: HistoryMsgItemType) => ({
|
||||||
id: item.questionId,
|
id: item.questionId,
|
||||||
type:
|
type:
|
||||||
item.queryResult?.queryMode === MessageTypeEnum.PLUGIN ||
|
|
||||||
item.queryResult?.queryMode === MessageTypeEnum.WEB_PAGE
|
item.queryResult?.queryMode === MessageTypeEnum.WEB_PAGE
|
||||||
? MessageTypeEnum.PLUGIN
|
? MessageTypeEnum.PLUGIN
|
||||||
: MessageTypeEnum.QUESTION,
|
: MessageTypeEnum.QUESTION,
|
||||||
@@ -212,6 +223,10 @@ const Chat: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeAgent = (agent?: AgentType) => {
|
||||||
|
setCurrentAgent(agent);
|
||||||
|
};
|
||||||
|
|
||||||
const initModels = async () => {
|
const initModels = async () => {
|
||||||
const res = await getModelList();
|
const res = await getModelList();
|
||||||
const modelList = getLeafList(res.data);
|
const modelList = getLeafList(res.data);
|
||||||
@@ -236,6 +251,7 @@ const Chat: React.FC<Props> = ({
|
|||||||
list?: MessageItem[],
|
list?: MessageItem[],
|
||||||
modelId?: number,
|
modelId?: number,
|
||||||
entityId?: string,
|
entityId?: string,
|
||||||
|
agent?: AgentType,
|
||||||
) => {
|
) => {
|
||||||
const currentMsg = msg || inputMsg;
|
const currentMsg = msg || inputMsg;
|
||||||
if (currentMsg.trim() === '') {
|
if (currentMsg.trim() === '') {
|
||||||
@@ -252,13 +268,26 @@ const Chat: React.FC<Props> = ({
|
|||||||
modelChanged = currentModel?.id !== toModel?.id;
|
modelChanged = currentModel?.id !== toModel?.id;
|
||||||
}
|
}
|
||||||
const modelIdValue = modelId || msgModel?.id || currentModel?.id;
|
const modelIdValue = modelId || msgModel?.id || currentModel?.id;
|
||||||
|
|
||||||
|
const msgAgent = agentList.find((item) => currentMsg.indexOf(item.name) === 1);
|
||||||
|
const certainAgent = currentMsg[0] === '/' && msgAgent;
|
||||||
|
const agentIdValue = certainAgent ? msgAgent.id : undefined;
|
||||||
|
if (agent || certainAgent) {
|
||||||
|
changeAgent(agent || msgAgent);
|
||||||
|
}
|
||||||
|
|
||||||
const msgs = [
|
const msgs = [
|
||||||
...(list || messageList),
|
...(list || messageList),
|
||||||
{
|
{
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
msg: currentMsg,
|
msg: currentMsg,
|
||||||
msgValue: certainModel ? currentMsg.replace(`@${msgModel.name}`, '').trim() : currentMsg,
|
msgValue: certainModel
|
||||||
|
? currentMsg.replace(`@${msgModel.name}`, '').trim()
|
||||||
|
: certainAgent
|
||||||
|
? currentMsg.replace(`/${certainAgent.name}`, '').trim()
|
||||||
|
: currentMsg,
|
||||||
modelId: modelIdValue === -1 ? undefined : modelIdValue,
|
modelId: modelIdValue === -1 ? undefined : modelIdValue,
|
||||||
|
agentId: agent?.id || agentIdValue || currentAgent?.id,
|
||||||
entityId: entityId || (modelChanged ? undefined : defaultEntity?.entityId),
|
entityId: entityId || (modelChanged ? undefined : defaultEntity?.entityId),
|
||||||
identityMsg: certainModel ? getIdentityMsgText(msgModel) : undefined,
|
identityMsg: certainModel ? getIdentityMsgText(msgModel) : undefined,
|
||||||
type: MessageTypeEnum.QUESTION,
|
type: MessageTypeEnum.QUESTION,
|
||||||
@@ -398,8 +427,22 @@ const Chat: React.FC<Props> = ({
|
|||||||
inputFocus();
|
inputFocus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSelectAgent = (agent: AgentType) => {
|
||||||
|
setCurrentAgent(agent);
|
||||||
|
setMessageList([
|
||||||
|
...messageList,
|
||||||
|
{
|
||||||
|
id: uuid(),
|
||||||
|
type: MessageTypeEnum.TEXT,
|
||||||
|
msg: `您好,智能助理【${agent.name}】将与您对话,可输入“/”切换助理`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
updateMessageContainerScroll();
|
||||||
|
};
|
||||||
|
|
||||||
const chatClass = classNames(styles.chat, {
|
const chatClass = classNames(styles.chat, {
|
||||||
[styles.mobile]: isMobileMode,
|
[styles.mobileMode]: isMobileMode,
|
||||||
|
[styles.mobile]: isMobile,
|
||||||
[styles.copilotFullscreen]: copilotFullscreen,
|
[styles.copilotFullscreen]: copilotFullscreen,
|
||||||
[styles.conversationCollapsed]: conversationCollapsed,
|
[styles.conversationCollapsed]: conversationCollapsed,
|
||||||
});
|
});
|
||||||
@@ -431,16 +474,21 @@ const Chat: React.FC<Props> = ({
|
|||||||
isMobileMode={isMobileMode}
|
isMobileMode={isMobileMode}
|
||||||
conversationCollapsed={conversationCollapsed}
|
conversationCollapsed={conversationCollapsed}
|
||||||
copilotFullscreen={copilotFullscreen}
|
copilotFullscreen={copilotFullscreen}
|
||||||
|
agentList={agentList}
|
||||||
onClickMessageContainer={inputFocus}
|
onClickMessageContainer={inputFocus}
|
||||||
onMsgDataLoaded={onMsgDataLoaded}
|
onMsgDataLoaded={onMsgDataLoaded}
|
||||||
onCheckMore={onCheckMore}
|
onCheckMore={onCheckMore}
|
||||||
onApplyAuth={onApplyAuth}
|
onApplyAuth={onApplyAuth}
|
||||||
|
onSendMsg={onSendMsg}
|
||||||
|
onSelectAgent={onSelectAgent}
|
||||||
/>
|
/>
|
||||||
<ChatFooter
|
<ChatFooter
|
||||||
inputMsg={inputMsg}
|
inputMsg={inputMsg}
|
||||||
chatId={currentConversation?.chatId}
|
chatId={currentConversation?.chatId}
|
||||||
models={models}
|
models={models}
|
||||||
|
agentList={agentList}
|
||||||
currentModel={currentModel}
|
currentModel={currentModel}
|
||||||
|
currentAgent={currentAgent}
|
||||||
defaultEntity={defaultEntity}
|
defaultEntity={defaultEntity}
|
||||||
collapsed={conversationCollapsed}
|
collapsed={conversationCollapsed}
|
||||||
isCopilotMode={isCopilotMode}
|
isCopilotMode={isCopilotMode}
|
||||||
@@ -461,6 +509,7 @@ const Chat: React.FC<Props> = ({
|
|||||||
onCancelCopilotFilter();
|
onCancelCopilotFilter();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onSelectAgent={onSelectAgent}
|
||||||
ref={chatFooterRef}
|
ref={chatFooterRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { request } from 'umi';
|
import { request } from 'umi';
|
||||||
import { ModelType } from './type';
|
import { AgentType, ModelType } from './type';
|
||||||
|
|
||||||
const prefix = '/api';
|
const prefix = '/api';
|
||||||
|
|
||||||
@@ -66,3 +66,9 @@ export function queryRecommendQuestions() {
|
|||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function queryAgentList() {
|
||||||
|
return request<Result<AgentType[]>>(`${prefix}/chat/agent/getAgentList`, {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -101,12 +101,12 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 20px 20px 60px 4px;
|
padding: 20px 20px 60px 4px;
|
||||||
row-gap: 10px;
|
row-gap: 16px;
|
||||||
|
|
||||||
.messageItem {
|
.messageItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
row-gap: 10px;
|
row-gap: 20px;
|
||||||
|
|
||||||
:global {
|
:global {
|
||||||
.ant-table-small {
|
.ant-table-small {
|
||||||
@@ -240,9 +240,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mobile {
|
&.mobileMode {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
|
||||||
.chatSection {
|
.chatSection {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
@@ -276,6 +275,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mobile {
|
||||||
|
height: 100vh !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation {
|
.conversation {
|
||||||
@@ -444,10 +447,6 @@
|
|||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// .messageItem {
|
|
||||||
// margin-top: 12px;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.messageTime {
|
.messageTime {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export enum MessageTypeEnum {
|
|||||||
WEB_PAGE = 'WEB_PAGE', // 插件
|
WEB_PAGE = 'WEB_PAGE', // 插件
|
||||||
RECOMMEND_QUESTIONS = 'recommend_questions', // 推荐问题
|
RECOMMEND_QUESTIONS = 'recommend_questions', // 推荐问题
|
||||||
PARSE_OPTIONS = 'parse_options', // 解析选项
|
PARSE_OPTIONS = 'parse_options', // 解析选项
|
||||||
|
AGENT_LIST = 'agent_list', // 专家列表
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageItem = {
|
export type MessageItem = {
|
||||||
@@ -20,6 +21,7 @@ export type MessageItem = {
|
|||||||
msgValue?: string;
|
msgValue?: string;
|
||||||
identityMsg?: string;
|
identityMsg?: string;
|
||||||
modelId?: number;
|
modelId?: number;
|
||||||
|
agentId?: number;
|
||||||
entityId?: string;
|
entityId?: string;
|
||||||
msgData?: MsgDataType;
|
msgData?: MsgDataType;
|
||||||
quote?: string;
|
quote?: string;
|
||||||
@@ -47,7 +49,6 @@ export enum MessageModeEnum {
|
|||||||
|
|
||||||
export type ModelType = {
|
export type ModelType = {
|
||||||
id: number;
|
id: number;
|
||||||
parentId: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
bizName: string;
|
bizName: string;
|
||||||
};
|
};
|
||||||
@@ -69,6 +70,7 @@ export type DefaultEntityType = {
|
|||||||
entityId: string;
|
entityId: string;
|
||||||
entityName: string;
|
entityName: string;
|
||||||
modelName?: string;
|
modelName?: string;
|
||||||
|
modelId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SuggestionItemType = {
|
export type SuggestionItemType = {
|
||||||
@@ -82,3 +84,11 @@ export type SuggestionType = {
|
|||||||
dimensions: SuggestionItemType[];
|
dimensions: SuggestionItemType[];
|
||||||
metrics: SuggestionItemType[];
|
metrics: SuggestionItemType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AgentType = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
examples: string[];
|
||||||
|
status: 0 | 1;
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import {
|
|||||||
} from './type';
|
} from './type';
|
||||||
import { getLeafList, uuid } from '@/utils/utils';
|
import { getLeafList, uuid } from '@/utils/utils';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
import { PARSE_MODE_MAP, PLUGIN_TYPE_MAP } from './constants';
|
import { PLUGIN_TYPE_MAP } from './constants';
|
||||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import { isArray, set } from 'lodash';
|
import { isArray } from 'lodash';
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
export const PLUGIN_TYPE_MAP = {
|
export const PLUGIN_TYPE_MAP = {
|
||||||
WEB_PAGE: '外链页面',
|
WEB_PAGE: 'Web页面',
|
||||||
WEB_SERVICE: 'Web服务',
|
WEB_SERVICE: 'Web服务',
|
||||||
DSL: 'LLM语义解析',
|
// DSL: 'LLM语义解析',
|
||||||
|
// CONTENT_INTERPRET: '内容解读',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PARSE_MODE_MAP = {
|
export const PARSE_MODE_MAP = {
|
||||||
|
|||||||
Reference in New Issue
Block a user