Files
supersonic/webapp/packages/chat-sdk/src/Chat/index.tsx
tristanliu 840de515a6 [improvement][headless-be] Optimized the logic for intent confirmation and streamlined mode in the question-answering dialogue of the assistant module. (#1860)
* [improvement][semantic-fe] Changing the line type for canvas connections.

* [improvement][semantic-fe] Replacing the initialization variable from "semantic" to "headless".

* [improvement][semantic-fe] Fixing the missing migration issue for default drill-down dimension configuration in model editing. Additionally, optimizing the data retrieval method for initializing fields in the model.

* [improvement][semantic-fe] Updating the logic for the fieldName.

* [improvement][semantic-fe] Adjusting the position of the metrics tab.

* [improvement][semantic-fe] Changing the 字段名称 to 英文名称.

* [improvement][semantic-fe] Fix metric measurement deletion.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI optimization for metric details page.

* [improvement][semantic-fe] UI adjustment for metric details page.

* [improvement][semantic-fe] The granularity field in the time type of model editing now supports setting it as empty.

* [improvement][semantic-fe] Added field type and metric type to the metric creation options.

* [improvement][semantic-fe] The organization structure selection feature has been added to the permission management.

* [improvement][semantic-fe] Improved user experience for the metric list.

* [improvement][semantic-fe] fix update the metric list.

* [improvement][headless-fe] Added view management functionality.

* [improvement][headless-fe] The view management functionality has been added. This feature allows users to create, edit, and manage different views within the system.

* [improvement][headless-fe] Added model editing side effect detection.

* [improvement][headless-fe] Fixed the logic error in view editing.

* [improvement][headless-fe] Fixed the issue with initializing dimension associations in metric settings.

* [improvement][headless-fe] Added the ability to hide the Q&A settings entry point.

* [improvement][headless-fe] Fixed the issue with selecting search results in metric field creation.

* [improvement][headless-fe] Added search functionality to the field list in model editing.

* [improvement][headless-fe] fix the field list in model editing

* [improvement][headless-fe] Restructured the data for the dimension value settings interface.

* [improvement][headless-fe] Added dynamic variable functionality to model creation based on SQL scripts.

* [improvement][headless-fe] Added support for passing dynamic variables as parameters in the executeSql function.

* [improvement][headless-fe] Resolved the issue where users were unable to select all options for dimensions, metrics, and fields in the metric generation process.

* [improvement][headless-fe] Replaced the term "view" with "dataset"

* [improvement][headless-fe] Added the ability to export metrics and dimensions to a specific target.

* [improvement][headless-fe] Enhanced dataset creation to support the tag mode.

* [improvement][headless-fe] Added tag value setting.

* [improvement][headless-fe] Optimized the tag setting system.

* [improvement][headless-fe] Optimized the tag setting system.

* [improvement][headless-fe] Updated the data initialization for model editing to use API requests instead.

* [improvement][headless-fe] Added search functionality to model management.

* [improvement][headless-fe] Removed field null validation during model editing.

* [improvement][headless-fe] Updated the batch operation button component.

* [improvement][headless-fe] Optimized the logic for initializing indicators in dimension value settings.

* [improvement][headless-fe] Adjusted the length of the input field for model editing names.

* [improvement][headless-fe]  Lock the version of the @ant-design/pro-table component and replace it with @ant-design/pro-components.

* [improvement][headless-fe] Optimized the style of the metrics market and tags market.

* [improvement][headless-fe] The quick creation of model fields now defaults to using the "comment" field for filling.

* [improvement][headless-fe] The quick creation of model fields now defaults to using the "comment" field for filling

* [improvement][headless-fe] The quick creation of model fields now defaults to using the "comment" field for filling.

* [improvement][headless-fe] Fixed the issue where the conditions for metric measurement creation were not being saved correctly.

* [improvement][headless-fe] Default value setting for hiding dimensions.

* [improvement][headless-fe] Updated the file imports in the project.

* [improvement][headless-fe] Adjusted the logic for displaying the tab in the theme domain.

* [improvement][headless-fe] Added term management functionality.

* [improvement][headless-fe] When creating a model, the current metric operator now allows for clearance.

* [improvement][headless-fe] Term management interface transformation

* [improvement][headless-fe] Migrating scaffold version to @umi/max

* [improvement][headless-fe] remove modle column

* [improvement][headless-fe] 1.Added configuration for the large language model in the agent; 2.upgraded React version from 17 to 18; 3.modified some UI effects.

* [improvement][headless-fe] Added a simplified mode to the question-answering system.

* [improvement][headless-fe] remove pnpm-lock

* [improvement][headless-fe] add pnpm-lock

* [improvement][headless-fe] Fixed the issue with passing the modelId during initialization.

* [improvement][headless-fe] Fixed the issue with abnormal comments during model creation.

* [improvement][headless-fe] fix  build bugs

* [improvement][headless-fe]  change build config

* [improvement][headless-fe] route config change

* [improvement][headless-fe] Optimized data updating when switching domains.

* [improvement][headless-fe] css change

* [improvement][semantic-fe] logo css change

* [improvement][semantic-fe] system config add defaultValue support

* [improvement][semantic-fe] tag mode wording change

* [improvement][semantic-fe] fix metric edit page init error

* [improvement][supersonic-fe] Updated the way chat projects are imported in supersonic-fe.

* [improvement][chat-engine] Added a background silent mode for watching chat projects.

* [improvement][supersonic-fe] fix proxy url

* [improvement][headless-fe] agent setting update

* [improvement][headless-fe] Agent configuration: Added connectivity testing for large models.

* [improvement][headless-fe] Chat: Enabled integration with agent configuration.

* [improvement][headless-fe] SQL formatter: Updated the import method.

* [improvement][headless-fe] login fixed

* [improvement][headless-fe] Agent: Optimized the logic for saving.

* [improvement][headless-fe] Model management: Integrated into the main theme domain.

* [improvement][headless-fe] Sensitivity: Added bulk modification functionality.

* [improvement][headless-fe] wording change

* [improvement][headless-fe] Prompt configuration: Added the ability to configure prompts.

* [improvement][headless-fe] Added the ability to configure embedding models.

* [improvement][headless-fe] hidden configure embedding models.

* [improvement][headless-fe] Connection test prompt update for large language model settings.

* [improvement][headless-fe]  add memory review config

* [improvement][headless-fe] Rollback of data structure for large language model configuration.

* [improvement][headless-fe] Added dependency relationships between various items in system configuration.

* [improvement][headless-fe] Added password parsing to the generation rules of system settings.

* [improvement][headless-fe] Added slider parsing to the generation rules of system settings.

* [improvement][headless-fe] Optimized the logic for initializing values in system settings.

* [improvement][headless-fe] Optimized the time format

* [improvement][headless-fe] Unified the SQL generation method for data sets to ensure consistency and improve efficiency.

* [improvement][headless-fe] Added support for data sets with non-partitioned time fields

* [improvement][headless-fe] Added support for editing time formats in dimension settings

* [improvement][headless-fe] Rolled back the time parameter in the metric details page to its previous state

* [improvement][headless-fe] Fixed the issue with hidden field validation when editing dimensions

* [improvement][headless-fe] Added a connectivity test for large models in the system settings

* [improvement][headless-fe] Changed the METRIC type in ChatContextType queryType to AGGREGATE

* [improvement][headless-fe] Added query and batch delete operations to the terminology management feature

* [improvement][headless-fe] Enhanced the memory management feature by adding sorting functionality and other optimization features.

* [improvement][headless-fe] Resolved the compatibility issue with the backend service where the updatedAt and other time fields were not being properly converted to the updated_at format for sorting purposes.

* [improvement][headless-fe] Added a configuration interface for large language models

* [improvement][headless-fe] Introduced a new configuration option in the assistant module specifically for large model applications.

* [improvement][headless-fe] Replaced the agentConfig with toolConfig

* [improvement][headless-fe] Resolved the issue with the test connection parameters for the large model configuration.

* [improvement][headless-fe] Implemented a new feature where the tool configuration types can be fetched from the backend API

* [improvement][headless-fe] Updated the dimension value settings to retrieve and display values from a dictionary.

* [improvement][headless-fe] Adjusted the pageSize of the dimension value settings list to 20.

* [improvement][headless-fe] Introduced a revamped configuration for the large model in the assistant module.

* [improvement][headless-fe] Added new functionality to the assistant's memory management system

* [improvement][headless-fe] Optimized the management of CSS styles in the assistant module.

* [improvement][headless] fixed build config

* [improvement][headless-fe] Revamped the dimension value settings in the assistant module.

* [improvement][headless-fe] Optimized the initialization process of dimension value settings in the assistant module.

* [improvement][headless-fe] Added support for user confirmation mode in the question-answering dialogue of the assistant module.

* [improvement][headless-be] As part of the ongoing improvements to the question-answering dialogue, the datasetId will now be included as a parameter in the API endpoint /api/chat/query/search for querying the question-answering dataset.

* [improvement][headless-be] Optimized the logic for intent confirmation and streamlined mode in the question-answering dialogue of the assistant module.

---------

Co-authored-by: tristanliu <tristanliu@tencent.com>
2024-10-30 17:22:37 +08:00

527 lines
16 KiB
TypeScript

import { updateMessageContainerScroll, isMobile, uuid, setToken } from '../utils/utils';
import {
ForwardRefRenderFunction,
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import MessageContainer from './MessageContainer';
import styles from './style.module.less';
import { ConversationDetailType, MessageItem, MessageTypeEnum, AgentType } from './type';
import { queryAgentList } from './service';
import { useThrottleFn } from 'ahooks';
import Conversation from './Conversation';
import ChatFooter from './ChatFooter';
import classNames from 'classnames';
import { cloneDeep, isBoolean } from 'lodash';
import AgentList from './AgentList';
import MobileAgents from './MobileAgents';
import { HistoryMsgItemType, MsgDataType, SendMsgParamsType } from '../common/type';
import { getHistoryMsg } from '../service';
import ShowCase from '../ShowCase';
import { jsonParse } from '../utils/utils';
import { ConfigProvider, Drawer, Modal, Row, Col, Space, Switch, Tooltip } from 'antd';
import locale from 'antd/locale/zh_CN';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
type Props = {
token?: string;
agentIds?: number[];
initialAgentId?: number;
chatVisible?: boolean;
noInput?: boolean;
isDeveloper?: boolean;
integrateSystem?: string;
isCopilot?: boolean;
onCurrentAgentChange?: (agent?: AgentType) => void;
onReportMsgEvent?: (msg: string, valid: boolean) => void;
};
const Chat: ForwardRefRenderFunction<any, Props> = (
{
token,
agentIds,
initialAgentId,
chatVisible,
noInput,
isDeveloper,
integrateSystem,
isCopilot,
onCurrentAgentChange,
onReportMsgEvent,
},
ref
) => {
const [messageList, setMessageList] = useState<MessageItem[]>([]);
const [inputMsg, setInputMsg] = useState('');
const [pageNo, setPageNo] = useState(1);
const [hasNextPage, setHasNextPage] = useState(false);
const [historyInited, setHistoryInited] = useState(false);
const [currentConversation, setCurrentConversation] = useState<
ConversationDetailType | undefined
>(isMobile ? { chatId: 0, chatName: '问答' } : undefined);
const [historyVisible, setHistoryVisible] = useState(false);
const [agentList, setAgentList] = useState<AgentType[]>([]);
const [currentAgent, setCurrentAgent] = useState<AgentType>();
const [mobileAgentsVisible, setMobileAgentsVisible] = useState(false);
const [agentListVisible, setAgentListVisible] = useState(true);
const [showCaseVisible, setShowCaseVisible] = useState(false);
const [isSimpleMode, setIsSimpleMode] = useState<boolean>(false);
const [isDebugMode, setIsDebugMode] = useState<boolean>(true);
const conversationRef = useRef<any>();
const chatFooterRef = useRef<any>();
useImperativeHandle(ref, () => ({
sendCopilotMsg,
}));
const sendCopilotMsg = (params: SendMsgParamsType) => {
setAgentListVisible(false);
const { agentId, msg, modelId } = params;
if (currentAgent?.id !== agentId) {
setMessageList([]);
const agent = agentList.find(item => item.id === agentId) || ({} as AgentType);
updateCurrentAgent({ ...agent, initialSendMsgParams: params });
} else {
onSendMsg(msg, messageList, modelId, params);
}
};
const updateAgentConfigMode = (agent: AgentType) => {
const toolConfig = jsonParse(agent?.toolConfig, {});
const { simpleMode, debugMode } = toolConfig;
if (isBoolean(simpleMode)) {
setIsSimpleMode(simpleMode);
} else {
setIsSimpleMode(false);
}
if (isBoolean(debugMode)) {
setIsDebugMode(debugMode);
} else {
setIsDebugMode(true);
}
};
const updateCurrentAgent = (agent?: AgentType) => {
setCurrentAgent(agent);
onCurrentAgentChange?.(agent);
localStorage.setItem('AGENT_ID', `${agent?.id}`);
if (agent) {
updateAgentConfigMode(agent);
}
if (!isCopilot) {
window.history.replaceState({}, '', `${window.location.pathname}?agentId=${agent?.id}`);
}
};
const initAgentList = async () => {
const res = await queryAgentList();
const agentListValue = (res.data || []).filter(
item => item.status === 1 && (agentIds === undefined || agentIds.includes(item.id))
);
setAgentList(agentListValue);
if (agentListValue.length > 0) {
const agentId = initialAgentId || localStorage.getItem('AGENT_ID');
if (agentId) {
const agent = agentListValue.find(item => item.id === +agentId);
updateCurrentAgent(agent || agentListValue[0]);
} else {
updateCurrentAgent(agentListValue[0]);
}
}
};
useEffect(() => {
initAgentList();
}, []);
useEffect(() => {
if (token) {
setToken(token);
}
}, [token]);
useEffect(() => {
if (chatVisible) {
inputFocus();
updateMessageContainerScroll();
}
}, [chatVisible]);
useEffect(() => {
if (!currentConversation) {
return;
}
const { initialMsgParams, isAdd } = currentConversation;
if (isAdd) {
inputFocus();
if (initialMsgParams) {
onSendMsg(initialMsgParams.msg, [], initialMsgParams.modelId, initialMsgParams);
return;
}
sendHelloRsp();
return;
}
updateHistoryMsg(1);
setPageNo(1);
}, [currentConversation]);
useEffect(() => {
if (historyInited) {
const messageContainerEle = document.getElementById('messageContainer');
messageContainerEle?.addEventListener('scroll', handleScroll);
}
return () => {
const messageContainerEle = document.getElementById('messageContainer');
messageContainerEle?.removeEventListener('scroll', handleScroll);
};
}, [historyInited]);
const sendHelloRsp = (agent?: AgentType) => {
if (noInput) {
return;
}
setMessageList([
{
id: uuid(),
type: MessageTypeEnum.AGENT_LIST,
msg: agent?.name || currentAgent?.name || agentList?.[0]?.name,
},
]);
};
const convertHistoryMsg = (list: HistoryMsgItemType[]) => {
return list.map((item: HistoryMsgItemType) => ({
id: item.questionId,
questionId: item.questionId,
type: MessageTypeEnum.QUESTION,
msg: item.queryText,
parseInfos: item.parseInfos,
parseTimeCost: item.parseTimeCost,
msgData: { ...(item.queryResult || {}), similarQueries: item.similarQueries },
score: item.score,
agentId: currentAgent?.id,
}));
};
const updateHistoryMsg = async (page: number) => {
const res = await getHistoryMsg(page, currentConversation!.chatId, 3);
const { hasNextPage, list } = res?.data || { hasNextPage: false, list: [] };
const msgList = [...convertHistoryMsg(list), ...(page === 1 ? [] : messageList)];
setMessageList(msgList);
setHasNextPage(hasNextPage);
if (page === 1) {
if (list.length === 0) {
sendHelloRsp();
}
updateMessageContainerScroll();
setHistoryInited(true);
inputFocus();
} else {
const msgEle = document.getElementById(`${messageList[0]?.id}`);
msgEle?.scrollIntoView();
}
};
const { run: handleScroll } = useThrottleFn(
e => {
if (e.target.scrollTop === 0 && hasNextPage) {
updateHistoryMsg(pageNo + 1);
setPageNo(pageNo + 1);
}
},
{
leading: true,
trailing: true,
wait: 200,
}
);
const inputFocus = () => {
if (!isMobile) {
chatFooterRef.current?.inputFocus();
}
};
const inputBlur = () => {
chatFooterRef.current?.inputBlur();
};
const onSendMsg = async (
msg?: string,
list?: MessageItem[],
modelId?: number,
sendMsgParams?: SendMsgParamsType
) => {
const currentMsg = msg || inputMsg;
if (currentMsg.trim() === '') {
setInputMsg('');
return;
}
const msgAgent = agentList.find(item => currentMsg.indexOf(item.name) === 1);
const certainAgent = currentMsg[0] === '/' && msgAgent;
const agentIdValue = certainAgent ? msgAgent.id : undefined;
const agent = agentList.find(item => item.id === sendMsgParams?.agentId);
if (agent || certainAgent) {
updateCurrentAgent(agent || msgAgent);
}
const msgs = [
...(list || messageList),
{
id: uuid(),
msg: currentMsg,
msgValue: certainAgent
? currentMsg.replace(`/${certainAgent.name}`, '').trim()
: currentMsg,
modelId: modelId === -1 ? undefined : modelId,
agentId: agent?.id || agentIdValue || currentAgent?.id,
type: MessageTypeEnum.QUESTION,
filters: sendMsgParams?.filters,
},
];
setMessageList(msgs);
updateMessageContainerScroll();
setInputMsg('');
};
const onInputMsgChange = (value: string) => {
const inputMsgValue = value || '';
setInputMsg(inputMsgValue);
};
const saveConversationToLocal = (conversation: ConversationDetailType) => {
if (conversation) {
if (conversation.chatId !== -1) {
localStorage.setItem('CONVERSATION_ID', `${conversation.chatId}`);
}
} else {
localStorage.removeItem('CONVERSATION_ID');
}
};
const onSelectConversation = (
conversation: ConversationDetailType,
sendMsgParams?: SendMsgParamsType,
isAdd?: boolean
) => {
setCurrentConversation({
...conversation,
initialMsgParams: sendMsgParams,
isAdd,
});
saveConversationToLocal(conversation);
};
const onMsgDataLoaded = (
data: MsgDataType,
questionId: string | number,
question: string,
valid: boolean,
isRefresh?: boolean
) => {
onReportMsgEvent?.(question, valid);
if (!isMobile) {
conversationRef?.current?.updateData(currentAgent?.id);
}
if (!data) {
return;
}
const msgs = cloneDeep(messageList);
const msg = msgs.find(item => item.id === questionId);
if (msg) {
msg.msgData = data;
setMessageList(msgs);
}
if (!isRefresh) {
updateMessageContainerScroll(`${questionId}`);
}
};
const onToggleHistoryVisible = () => {
setHistoryVisible(!historyVisible);
};
const onAddConversation = () => {
conversationRef.current?.onAddConversation();
inputFocus();
};
const onSelectAgent = (agent: AgentType) => {
if (agent.id === currentAgent?.id) {
return;
}
if (messageList.length === 1 && messageList[0].type === MessageTypeEnum.AGENT_LIST) {
setMessageList([]);
}
updateCurrentAgent(agent);
updateMessageContainerScroll();
};
const sendMsg = (msg: string, modelId?: number) => {
onSendMsg(msg, messageList, modelId);
if (isMobile) {
inputBlur();
}
};
const onCloseConversation = () => {
setHistoryVisible(false);
};
const chatClass = classNames(styles.chat, {
[styles.mobile]: isMobile,
[styles.historyVisible]: historyVisible,
});
return (
<ConfigProvider locale={locale}>
<div className={chatClass}>
<div className={styles.chatSection}>
{!isMobile && agentList.length > 1 && agentListVisible && (
<AgentList
agentList={agentList}
currentAgent={currentAgent}
onSelectAgent={onSelectAgent}
/>
)}
<div className={styles.chatApp}>
{currentConversation && (
<div className={styles.chatBody}>
<div className={styles.chatContent}>
{currentAgent && !isMobile && !noInput && (
<div className={styles.chatHeader}>
<Row style={{ width: '100%' }}>
<Col flex="1 1 200px">
<Space>
<div className={styles.chatHeaderTitle}>{currentAgent.name}</div>
<div className={styles.chatHeaderTip}>{currentAgent.description}</div>
<Tooltip title="精简模式下,问答结果将以文本形式输出">
<Switch
key={currentAgent.id}
style={{ position: 'relative', top: -1 }}
size="small"
value={isSimpleMode}
checkedChildren="精简模式"
unCheckedChildren="精简模式"
onChange={checked => {
setIsSimpleMode(checked);
}}
/>
</Tooltip>
</Space>
</Col>
<Col flex="0 1 118px"></Col>
</Row>
</div>
)}
<MessageContainer
id="messageContainer"
isSimpleMode={isSimpleMode}
isDebugMode={isDebugMode}
messageList={messageList}
chatId={currentConversation?.chatId}
historyVisible={historyVisible}
currentAgent={currentAgent}
chatVisible={chatVisible}
isDeveloper={isDeveloper}
integrateSystem={integrateSystem}
onMsgDataLoaded={onMsgDataLoaded}
onSendMsg={onSendMsg}
/>
{!noInput && (
<ChatFooter
inputMsg={inputMsg}
chatId={currentConversation?.chatId}
agentList={agentList}
currentAgent={currentAgent}
onToggleHistoryVisible={onToggleHistoryVisible}
onInputMsgChange={onInputMsgChange}
onSendMsg={sendMsg}
onAddConversation={onAddConversation}
onSelectAgent={onSelectAgent}
onOpenAgents={() => {
if (isMobile) {
setMobileAgentsVisible(true);
} else {
setAgentListVisible(!agentListVisible);
}
}}
onOpenShowcase={() => {
setShowCaseVisible(!showCaseVisible);
}}
ref={chatFooterRef}
/>
)}
</div>
</div>
)}
</div>
<Conversation
currentAgent={currentAgent}
currentConversation={currentConversation}
historyVisible={historyVisible}
onSelectConversation={onSelectConversation}
onCloseConversation={onCloseConversation}
ref={conversationRef}
/>
{currentAgent &&
(isMobile ? (
<Drawer
title="showcase"
placement="bottom"
height="95%"
open={showCaseVisible}
className={styles.showCaseDrawer}
destroyOnClose
onClose={() => {
setShowCaseVisible(false);
}}
>
<ShowCase agentId={currentAgent.id} onSendMsg={onSendMsg} />
</Drawer>
) : (
<Modal
title="showcase"
width="98%"
open={showCaseVisible}
centered
footer={null}
wrapClassName={styles.showCaseModal}
destroyOnClose
onCancel={() => {
setShowCaseVisible(false);
}}
>
<ShowCase
height="calc(100vh - 140px)"
agentId={currentAgent.id}
onSendMsg={onSendMsg}
/>
</Modal>
))}
</div>
<MobileAgents
open={mobileAgentsVisible}
agentList={agentList}
currentAgent={currentAgent}
onSelectAgent={onSelectAgent}
onClose={() => {
setMobileAgentsVisible(false);
}}
/>
</div>
</ConfigProvider>
);
};
export default forwardRef(Chat);