mirror of
https://github.com/tencentmusic/supersonic.git
synced 2026-01-05 16:59:26 +08:00
[feature](weaapp) add agent
This commit is contained in:
@@ -8,24 +8,26 @@ import type { ForwardRefRenderFunction } from 'react';
|
||||
import { searchRecommend } from 'supersonic-chat-sdk';
|
||||
import { SemanticTypeEnum, SEMANTIC_TYPE_MAP } from '../constants';
|
||||
import styles from './style.less';
|
||||
import { PLACE_HOLDER } from '../constants';
|
||||
import { DefaultEntityType, ModelType } from '../type';
|
||||
import { DefaultEntityType, AgentType, ModelType } from '../type';
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
|
||||
|
||||
type Props = {
|
||||
inputMsg: string;
|
||||
chatId?: number;
|
||||
currentModel?: ModelType;
|
||||
currentAgent?: AgentType;
|
||||
defaultEntity?: DefaultEntityType;
|
||||
isCopilotMode?: boolean;
|
||||
copilotFullscreen?: boolean;
|
||||
models: ModelType[];
|
||||
agentList: AgentType[];
|
||||
collapsed: boolean;
|
||||
onToggleCollapseBtn: () => void;
|
||||
onInputMsgChange: (value: string) => void;
|
||||
onSendMsg: (msg: string, modelId?: number) => void;
|
||||
onAddConversation: () => void;
|
||||
onCancelDefaultFilter: () => void;
|
||||
onSelectAgent: (agent: AgentType) => void;
|
||||
};
|
||||
|
||||
const { OptGroup, Option } = Select;
|
||||
@@ -45,8 +47,10 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
||||
inputMsg,
|
||||
chatId,
|
||||
currentModel,
|
||||
currentAgent,
|
||||
defaultEntity,
|
||||
models,
|
||||
agentList,
|
||||
collapsed,
|
||||
isCopilotMode,
|
||||
copilotFullscreen,
|
||||
@@ -55,10 +59,11 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
||||
onSendMsg,
|
||||
onAddConversation,
|
||||
onCancelDefaultFilter,
|
||||
onSelectAgent,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [modelOptions, setModelOptions] = useState<ModelType[]>([]);
|
||||
const [modelOptions, setModelOptions] = useState<(ModelType | AgentType)[]>([]);
|
||||
const [stepOptions, setStepOptions] = useState<Record<string, any[]>>({});
|
||||
const [open, setOpen] = 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}`));
|
||||
msgValue = model ? msg.replace(`@${model.name}`, '') : msg;
|
||||
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 };
|
||||
};
|
||||
@@ -163,9 +171,9 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
||||
const [debounceGetWords] = useState<any>(debounceGetWordsFunc);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputMsg.length === 1 && inputMsg[0] === '@') {
|
||||
if (inputMsg.length === 1 && (inputMsg[0] === '@' || inputMsg[0] === '/')) {
|
||||
setOpen(true);
|
||||
setModelOptions(models);
|
||||
setModelOptions(inputMsg[0] === '/' ? agentList : models);
|
||||
setStepOptions({});
|
||||
return;
|
||||
} else {
|
||||
@@ -173,10 +181,10 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
||||
if (modelOptions.length > 0) {
|
||||
setTimeout(() => {
|
||||
setModelOptions([]);
|
||||
}, 500);
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
if (!isSelect) {
|
||||
if (!isSelect && currentAgent?.name !== '问知识') {
|
||||
debounceGetWords(inputMsg, models, chatId, currentModel);
|
||||
} else {
|
||||
isSelect = false;
|
||||
@@ -237,6 +245,12 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
||||
isSelect = true;
|
||||
if (modelOptions.length === 0) {
|
||||
sendMsg(value);
|
||||
} else {
|
||||
const agent = agentList.find((item) => value.includes(item.name));
|
||||
if (agent) {
|
||||
onSelectAgent(agent);
|
||||
onInputMsgChange('');
|
||||
}
|
||||
}
|
||||
setOpen(false);
|
||||
setTimeout(() => {
|
||||
@@ -249,12 +263,98 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
||||
[styles.defaultCopilotMode]: isCopilotMode && !copilotFullscreen,
|
||||
});
|
||||
|
||||
const restrictNode = currentModel && !isMobile && (
|
||||
<div className={styles.currentModel}>
|
||||
<div className={styles.currentModelName}>
|
||||
输入联想与问题回复将限定于:“
|
||||
<span className={styles.quoteText}>
|
||||
{!defaultEntity && <>主题域【{currentModel.name}】</>}
|
||||
{defaultEntity && (
|
||||
<>
|
||||
<span>{`${currentModel.name.slice(0, currentModel.name.length - 1)}【`}</span>
|
||||
<span className={styles.entityName} title={defaultEntity.entityName}>
|
||||
{defaultEntity.entityName}
|
||||
</span>
|
||||
<span>】</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
”
|
||||
</div>
|
||||
<div className={styles.cancelModel} onClick={onCancelDefaultFilter}>
|
||||
取消限定
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const modelOptionNodes = modelOptions.map((model) => {
|
||||
return (
|
||||
<Option
|
||||
key={model.id}
|
||||
value={inputMsg[0] === '/' ? `/${model.name} ` : `@${model.name} `}
|
||||
className={styles.searchOption}
|
||||
>
|
||||
{model.name}
|
||||
</Option>
|
||||
);
|
||||
});
|
||||
|
||||
const associateOptionNodes = Object.keys(stepOptions).map((key) => {
|
||||
return (
|
||||
<OptGroup key={key} label={key}>
|
||||
{stepOptions[key].map((option) => {
|
||||
let optionValue =
|
||||
Object.keys(stepOptions).length === 1
|
||||
? option.recommend
|
||||
: `${option.modelName || ''}${option.recommend}`;
|
||||
if (inputMsg[0] === '@') {
|
||||
const model = models.find((item) => inputMsg.includes(item.name));
|
||||
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 (
|
||||
<Option
|
||||
key={`${option.recommend}${option.modelName ? `_${option.modelName}` : ''}`}
|
||||
value={optionValue}
|
||||
className={styles.searchOption}
|
||||
>
|
||||
<div className={styles.optionContent}>
|
||||
{option.schemaElementType && (
|
||||
<Tag
|
||||
className={styles.semanticType}
|
||||
color={
|
||||
option.schemaElementType === SemanticTypeEnum.DIMENSION ||
|
||||
option.schemaElementType === SemanticTypeEnum.MODEL
|
||||
? 'blue'
|
||||
: option.schemaElementType === SemanticTypeEnum.VALUE
|
||||
? 'geekblue'
|
||||
: 'cyan'
|
||||
}
|
||||
>
|
||||
{SEMANTIC_TYPE_MAP[option.schemaElementType] ||
|
||||
option.schemaElementType ||
|
||||
'维度'}
|
||||
</Tag>
|
||||
)}
|
||||
{option.subRecommend}
|
||||
</div>
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</OptGroup>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={chatFooterClass}>
|
||||
<div className={styles.composer}>
|
||||
<div className={styles.collapseBtn} onClick={onToggleCollapseBtn}>
|
||||
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</div>
|
||||
{!isMobile && (
|
||||
<div className={styles.collapseBtn} onClick={onToggleCollapseBtn}>
|
||||
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</div>
|
||||
)}
|
||||
<Tooltip title="新建对话">
|
||||
<IconFont
|
||||
type="icon-icon-add-conversation-line"
|
||||
@@ -263,36 +363,14 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
||||
/>
|
||||
</Tooltip>
|
||||
<div className={styles.composerInputWrapper}>
|
||||
{currentModel && (
|
||||
<div className={styles.currentModel}>
|
||||
<div className={styles.currentModelName}>
|
||||
输入联想与问题回复将限定于:“
|
||||
<span className={styles.quoteText}>
|
||||
主题域【{currentModel.name}】
|
||||
{defaultEntity && (
|
||||
<>
|
||||
<span>,</span>
|
||||
<span>{`${currentModel.name.slice(0, currentModel.name.length - 1)}【`}</span>
|
||||
<span className={styles.entityName} title={defaultEntity.entityName}>
|
||||
{defaultEntity.entityName}
|
||||
</span>
|
||||
<span>】</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
”
|
||||
</div>
|
||||
<div className={styles.cancelModel} onClick={onCancelDefaultFilter}>
|
||||
取消限定
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* {restrictNode}
|
||||
{currentAgentNode} */}
|
||||
<AutoComplete
|
||||
className={styles.composerInput}
|
||||
placeholder={
|
||||
currentModel
|
||||
? `请输入【${currentModel.name}】主题的问题,可使用@切换到其他主题`
|
||||
: PLACE_HOLDER
|
||||
currentAgent?.name
|
||||
? `智能助理【${currentAgent?.name}】将与您对话,可输入“/”切换助理`
|
||||
: '请输入您的问题'
|
||||
}
|
||||
value={inputMsg}
|
||||
onChange={onInputMsgChange}
|
||||
@@ -302,10 +380,20 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
||||
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);
|
||||
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={() => {
|
||||
@@ -320,64 +408,7 @@ const ChatFooter: ForwardRefRenderFunction<any, Props> = (
|
||||
open={open}
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||
>
|
||||
{modelOptions.length > 0
|
||||
? modelOptions.map((model) => {
|
||||
return (
|
||||
<Option
|
||||
key={model.id}
|
||||
value={`@${model.name} `}
|
||||
className={styles.searchOption}
|
||||
>
|
||||
{model.name}
|
||||
</Option>
|
||||
);
|
||||
})
|
||||
: Object.keys(stepOptions).map((key) => {
|
||||
return (
|
||||
<OptGroup key={key} label={key}>
|
||||
{stepOptions[key].map((option) => {
|
||||
let optionValue =
|
||||
Object.keys(stepOptions).length === 1
|
||||
? option.recommend
|
||||
: `${option.modelName || ''}${option.recommend}`;
|
||||
if (inputMsg[0] === '@') {
|
||||
const model = models.find((item) => inputMsg.includes(item.name));
|
||||
optionValue = model ? `@${model.name} ${option.recommend}` : optionValue;
|
||||
}
|
||||
return (
|
||||
<Option
|
||||
key={`${option.recommend}${
|
||||
option.modelName ? `_${option.modelName}` : ''
|
||||
}`}
|
||||
value={optionValue}
|
||||
className={styles.searchOption}
|
||||
>
|
||||
<div className={styles.optionContent}>
|
||||
{option.schemaElementType && (
|
||||
<Tag
|
||||
className={styles.semanticType}
|
||||
color={
|
||||
option.schemaElementType === SemanticTypeEnum.DIMENSION ||
|
||||
option.schemaElementType === SemanticTypeEnum.MODEL
|
||||
? 'blue'
|
||||
: option.schemaElementType === SemanticTypeEnum.VALUE
|
||||
? 'geekblue'
|
||||
: 'cyan'
|
||||
}
|
||||
>
|
||||
{SEMANTIC_TYPE_MAP[option.schemaElementType] ||
|
||||
option.schemaElementType ||
|
||||
'维度'}
|
||||
</Tag>
|
||||
)}
|
||||
{option.subRecommend}
|
||||
</div>
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</OptGroup>
|
||||
);
|
||||
})}
|
||||
{modelOptions.length > 0 ? modelOptionNodes : associateOptionNodes}
|
||||
</AutoComplete>
|
||||
<div
|
||||
className={classNames(styles.sendBtn, {
|
||||
|
||||
@@ -90,7 +90,12 @@ const Conversation: ForwardRefRenderFunction<any, Props> = (
|
||||
defaultEntityFilter?.entityName && window.location.pathname.includes('detail')
|
||||
? defaultEntityFilter.entityName
|
||||
: defaultModelName;
|
||||
onAddConversation({ name: conversationName, type: 'CUSTOMIZE' });
|
||||
onAddConversation({
|
||||
name: conversationName,
|
||||
type: 'CUSTOMIZE',
|
||||
modelId: defaultEntityFilter?.modelId,
|
||||
entityId: defaultEntityFilter?.entityId,
|
||||
});
|
||||
onNewConversationTriggered?.();
|
||||
}
|
||||
}, [triggerNewConversation]);
|
||||
|
||||
@@ -3,10 +3,13 @@ import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { isEqual } from 'lodash';
|
||||
import { ChatItem } 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 { updateMessageContainerScroll } from '@/utils/utils';
|
||||
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 = {
|
||||
id: string;
|
||||
@@ -15,6 +18,7 @@ type Props = {
|
||||
isMobileMode?: boolean;
|
||||
conversationCollapsed: boolean;
|
||||
copilotFullscreen?: boolean;
|
||||
agentList: AgentType[];
|
||||
onClickMessageContainer: () => void;
|
||||
onMsgDataLoaded: (
|
||||
data: MsgDataType,
|
||||
@@ -24,6 +28,8 @@ type Props = {
|
||||
) => void;
|
||||
onCheckMore: (data: MsgDataType) => void;
|
||||
onApplyAuth: (model: string) => void;
|
||||
onSendMsg: (value: string) => void;
|
||||
onSelectAgent: (agent: AgentType) => void;
|
||||
};
|
||||
|
||||
const MessageContainer: React.FC<Props> = ({
|
||||
@@ -33,10 +39,12 @@ const MessageContainer: React.FC<Props> = ({
|
||||
isMobileMode,
|
||||
conversationCollapsed,
|
||||
copilotFullscreen,
|
||||
agentList,
|
||||
onClickMessageContainer,
|
||||
onMsgDataLoaded,
|
||||
onCheckMore,
|
||||
onApplyAuth,
|
||||
onSendMsg,
|
||||
onSelectAgent,
|
||||
}) => {
|
||||
const [triggerResize, setTriggerResize] = useState(false);
|
||||
|
||||
@@ -97,6 +105,7 @@ const MessageContainer: React.FC<Props> = ({
|
||||
}
|
||||
return [
|
||||
{
|
||||
...MODEL_MODEL_ENTITY_ID_FILTER_MAP[modelId],
|
||||
value: entityId,
|
||||
},
|
||||
];
|
||||
@@ -109,6 +118,7 @@ const MessageContainer: React.FC<Props> = ({
|
||||
const {
|
||||
id: msgId,
|
||||
modelId,
|
||||
agentId,
|
||||
entityId,
|
||||
type,
|
||||
msg,
|
||||
@@ -125,6 +135,17 @@ const MessageContainer: React.FC<Props> = ({
|
||||
return (
|
||||
<div key={msgId} id={`${msgId}`} className={styles.messageItem}>
|
||||
{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 && (
|
||||
<>
|
||||
<Text position="right" data={msg} />
|
||||
@@ -134,6 +155,7 @@ const MessageContainer: React.FC<Props> = ({
|
||||
msgData={msgData}
|
||||
conversationId={chatId}
|
||||
modelId={modelId}
|
||||
agentId={agentId}
|
||||
filter={getFilters(modelId, entityId)}
|
||||
isLastMessage={index === messageList.length - 1}
|
||||
isMobileMode={isMobileMode}
|
||||
@@ -150,6 +172,7 @@ const MessageContainer: React.FC<Props> = ({
|
||||
msg={msgValue || msg || ''}
|
||||
conversationId={chatId}
|
||||
modelId={modelId}
|
||||
agentId={agentId}
|
||||
filter={getFilters(modelId, entityId)}
|
||||
isLastMessage={index === messageList.length - 1}
|
||||
isMobileMode={isMobileMode}
|
||||
@@ -192,7 +215,8 @@ function areEqual(prevProps: Props, nextProps: Props) {
|
||||
prevProps.id === nextProps.id &&
|
||||
isEqual(prevProps.messageList, nextProps.messageList) &&
|
||||
prevProps.conversationCollapsed === nextProps.conversationCollapsed &&
|
||||
prevProps.copilotFullscreen === nextProps.copilotFullscreen
|
||||
prevProps.copilotFullscreen === nextProps.copilotFullscreen &&
|
||||
prevProps.agentList === nextProps.agentList
|
||||
) {
|
||||
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,
|
||||
MessageItem,
|
||||
MessageTypeEnum,
|
||||
AgentType,
|
||||
} from './type';
|
||||
import { getModelList } from './service';
|
||||
import { getModelList, queryAgentList } from './service';
|
||||
import { useThrottleFn } from 'ahooks';
|
||||
import Conversation from './Conversation';
|
||||
import ChatFooter from './ChatFooter';
|
||||
@@ -64,6 +65,8 @@ const Chat: React.FC<Props> = ({
|
||||
const [applyAuthVisible, setApplyAuthVisible] = useState(false);
|
||||
const [applyAuthModel, setApplyAuthModel] = useState('');
|
||||
const [initialModelName, setInitialModelName] = useState('');
|
||||
const [agentList, setAgentList] = useState<AgentType[]>([]);
|
||||
const [currentAgent, setCurrentAgent] = useState<AgentType>();
|
||||
const location = useLocation();
|
||||
const dispatch = useDispatch();
|
||||
const { modelName } = (location as any).query;
|
||||
@@ -71,9 +74,19 @@ const Chat: React.FC<Props> = ({
|
||||
const conversationRef = 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(() => {
|
||||
setChatSdkToken(localStorage.getItem(AUTH_TOKEN_KEY) || '');
|
||||
initModels();
|
||||
initAgentList();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -102,7 +115,13 @@ const Chat: React.FC<Props> = ({
|
||||
if (initMsg) {
|
||||
inputFocus();
|
||||
if (initMsg === 'CUSTOMIZE' && copilotSendMsg) {
|
||||
onSendMsg(copilotSendMsg, [], modelId, entityId);
|
||||
onSendMsg(
|
||||
copilotSendMsg,
|
||||
[],
|
||||
modelId,
|
||||
entityId,
|
||||
agentList.find((item) => item.name === '做分析'),
|
||||
);
|
||||
dispatch({
|
||||
type: 'globalState/setCopilotSendMsg',
|
||||
payload: '',
|
||||
@@ -143,16 +162,9 @@ const Chat: React.FC<Props> = ({
|
||||
setMessageList([
|
||||
{
|
||||
id: uuid(),
|
||||
type: MessageTypeEnum.TEXT,
|
||||
msg: defaultModelName
|
||||
? `您好,请输入关于${
|
||||
defaultEntityFilter?.entityName
|
||||
? `${defaultModelName?.slice(0, defaultModelName?.length - 1)}【${
|
||||
defaultEntityFilter?.entityName
|
||||
}】`
|
||||
: `【${defaultModelName}】`
|
||||
}的问题`
|
||||
: '您好,请问有什么我能帮您吗?',
|
||||
type: MessageTypeEnum.RECOMMEND_QUESTIONS,
|
||||
// type: MessageTypeEnum.AGENT_LIST,
|
||||
// msg: currentAgent?.name || '查信息',
|
||||
},
|
||||
]);
|
||||
};
|
||||
@@ -161,7 +173,6 @@ const Chat: React.FC<Props> = ({
|
||||
return list.map((item: HistoryMsgItemType) => ({
|
||||
id: item.questionId,
|
||||
type:
|
||||
item.queryResult?.queryMode === MessageTypeEnum.PLUGIN ||
|
||||
item.queryResult?.queryMode === MessageTypeEnum.WEB_PAGE
|
||||
? MessageTypeEnum.PLUGIN
|
||||
: MessageTypeEnum.QUESTION,
|
||||
@@ -212,6 +223,10 @@ const Chat: React.FC<Props> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const changeAgent = (agent?: AgentType) => {
|
||||
setCurrentAgent(agent);
|
||||
};
|
||||
|
||||
const initModels = async () => {
|
||||
const res = await getModelList();
|
||||
const modelList = getLeafList(res.data);
|
||||
@@ -236,6 +251,7 @@ const Chat: React.FC<Props> = ({
|
||||
list?: MessageItem[],
|
||||
modelId?: number,
|
||||
entityId?: string,
|
||||
agent?: AgentType,
|
||||
) => {
|
||||
const currentMsg = msg || inputMsg;
|
||||
if (currentMsg.trim() === '') {
|
||||
@@ -252,13 +268,26 @@ const Chat: React.FC<Props> = ({
|
||||
modelChanged = currentModel?.id !== toModel?.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 = [
|
||||
...(list || messageList),
|
||||
{
|
||||
id: uuid(),
|
||||
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,
|
||||
agentId: agent?.id || agentIdValue || currentAgent?.id,
|
||||
entityId: entityId || (modelChanged ? undefined : defaultEntity?.entityId),
|
||||
identityMsg: certainModel ? getIdentityMsgText(msgModel) : undefined,
|
||||
type: MessageTypeEnum.QUESTION,
|
||||
@@ -398,8 +427,22 @@ const Chat: React.FC<Props> = ({
|
||||
inputFocus();
|
||||
};
|
||||
|
||||
const onSelectAgent = (agent: AgentType) => {
|
||||
setCurrentAgent(agent);
|
||||
setMessageList([
|
||||
...messageList,
|
||||
{
|
||||
id: uuid(),
|
||||
type: MessageTypeEnum.TEXT,
|
||||
msg: `您好,智能助理【${agent.name}】将与您对话,可输入“/”切换助理`,
|
||||
},
|
||||
]);
|
||||
updateMessageContainerScroll();
|
||||
};
|
||||
|
||||
const chatClass = classNames(styles.chat, {
|
||||
[styles.mobile]: isMobileMode,
|
||||
[styles.mobileMode]: isMobileMode,
|
||||
[styles.mobile]: isMobile,
|
||||
[styles.copilotFullscreen]: copilotFullscreen,
|
||||
[styles.conversationCollapsed]: conversationCollapsed,
|
||||
});
|
||||
@@ -431,16 +474,21 @@ const Chat: React.FC<Props> = ({
|
||||
isMobileMode={isMobileMode}
|
||||
conversationCollapsed={conversationCollapsed}
|
||||
copilotFullscreen={copilotFullscreen}
|
||||
agentList={agentList}
|
||||
onClickMessageContainer={inputFocus}
|
||||
onMsgDataLoaded={onMsgDataLoaded}
|
||||
onCheckMore={onCheckMore}
|
||||
onApplyAuth={onApplyAuth}
|
||||
onSendMsg={onSendMsg}
|
||||
onSelectAgent={onSelectAgent}
|
||||
/>
|
||||
<ChatFooter
|
||||
inputMsg={inputMsg}
|
||||
chatId={currentConversation?.chatId}
|
||||
models={models}
|
||||
agentList={agentList}
|
||||
currentModel={currentModel}
|
||||
currentAgent={currentAgent}
|
||||
defaultEntity={defaultEntity}
|
||||
collapsed={conversationCollapsed}
|
||||
isCopilotMode={isCopilotMode}
|
||||
@@ -461,6 +509,7 @@ const Chat: React.FC<Props> = ({
|
||||
onCancelCopilotFilter();
|
||||
}
|
||||
}}
|
||||
onSelectAgent={onSelectAgent}
|
||||
ref={chatFooterRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { request } from 'umi';
|
||||
import { ModelType } from './type';
|
||||
import { AgentType, ModelType } from './type';
|
||||
|
||||
const prefix = '/api';
|
||||
|
||||
@@ -66,3 +66,9 @@ export function queryRecommendQuestions() {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
export function queryAgentList() {
|
||||
return request<Result<AgentType[]>>(`${prefix}/chat/agent/getAgentList`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,12 +101,12 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px 20px 60px 4px;
|
||||
row-gap: 10px;
|
||||
row-gap: 16px;
|
||||
|
||||
.messageItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 10px;
|
||||
row-gap: 20px;
|
||||
|
||||
:global {
|
||||
.ant-table-small {
|
||||
@@ -240,9 +240,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
&.mobileMode {
|
||||
height: 100% !important;
|
||||
|
||||
.chatSection {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
@@ -276,6 +275,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
height: 100vh !important;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation {
|
||||
@@ -444,10 +447,6 @@
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
// .messageItem {
|
||||
// margin-top: 12px;
|
||||
// }
|
||||
|
||||
.messageTime {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -11,6 +11,7 @@ export enum MessageTypeEnum {
|
||||
WEB_PAGE = 'WEB_PAGE', // 插件
|
||||
RECOMMEND_QUESTIONS = 'recommend_questions', // 推荐问题
|
||||
PARSE_OPTIONS = 'parse_options', // 解析选项
|
||||
AGENT_LIST = 'agent_list', // 专家列表
|
||||
}
|
||||
|
||||
export type MessageItem = {
|
||||
@@ -20,6 +21,7 @@ export type MessageItem = {
|
||||
msgValue?: string;
|
||||
identityMsg?: string;
|
||||
modelId?: number;
|
||||
agentId?: number;
|
||||
entityId?: string;
|
||||
msgData?: MsgDataType;
|
||||
quote?: string;
|
||||
@@ -47,7 +49,6 @@ export enum MessageModeEnum {
|
||||
|
||||
export type ModelType = {
|
||||
id: number;
|
||||
parentId: number;
|
||||
name: string;
|
||||
bizName: string;
|
||||
};
|
||||
@@ -69,6 +70,7 @@ export type DefaultEntityType = {
|
||||
entityId: string;
|
||||
entityName: string;
|
||||
modelName?: string;
|
||||
modelId?: number;
|
||||
};
|
||||
|
||||
export type SuggestionItemType = {
|
||||
@@ -82,3 +84,11 @@ export type SuggestionType = {
|
||||
dimensions: SuggestionItemType[];
|
||||
metrics: SuggestionItemType[];
|
||||
};
|
||||
|
||||
export type AgentType = {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
examples: string[];
|
||||
status: 0 | 1;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user