### What problem does this PR solve? Feat: Share agent dialog box externally #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| @@ -27,6 +27,7 @@ interface IProps { | |||
| showLikeButton: boolean; | |||
| audioBinary?: string; | |||
| showLoudspeaker?: boolean; | |||
| showLog?: boolean; | |||
| } | |||
| export const AssistantGroupButton = ({ | |||
| @@ -36,6 +37,7 @@ export const AssistantGroupButton = ({ | |||
| audioBinary, | |||
| showLikeButton, | |||
| showLoudspeaker = true, | |||
| showLog = true, | |||
| }: IProps) => { | |||
| const { visible, hideModal, showModal, onFeedbackOk, loading } = | |||
| useSendFeedback(messageId); | |||
| @@ -91,9 +93,11 @@ export const AssistantGroupButton = ({ | |||
| <PromptIcon style={{ fontSize: '16px' }} /> | |||
| </Radio.Button> | |||
| )} | |||
| <ToggleGroupItem value="f" onClick={handleShowLogSheet}> | |||
| <NotebookText className="size-4" /> | |||
| </ToggleGroupItem> | |||
| {showLog && ( | |||
| <ToggleGroupItem value="f" onClick={handleShowLogSheet}> | |||
| <NotebookText className="size-4" /> | |||
| </ToggleGroupItem> | |||
| )} | |||
| </ToggleGroup> | |||
| {visible && ( | |||
| <FeedbackModal | |||
| @@ -49,6 +49,7 @@ interface IProps | |||
| index: number; | |||
| showLikeButton?: boolean; | |||
| showLoudspeaker?: boolean; | |||
| showLog?: boolean; | |||
| } | |||
| function MessageItem({ | |||
| @@ -65,6 +66,7 @@ function MessageItem({ | |||
| showLoudspeaker = true, | |||
| visibleAvatar = true, | |||
| children, | |||
| showLog, | |||
| }: IProps) { | |||
| const { theme } = useTheme(); | |||
| const isAssistant = item.role === MessageType.Assistant; | |||
| @@ -141,6 +143,7 @@ function MessageItem({ | |||
| showLikeButton={showLikeButton} | |||
| audioBinary={item.audio_binary} | |||
| showLoudspeaker={showLoudspeaker} | |||
| showLog={showLog} | |||
| ></AssistantGroupButton> | |||
| ) : ( | |||
| <UserGroupButton | |||
| @@ -7,6 +7,7 @@ export enum UserSettingRouteKey { | |||
| System = 'system', | |||
| Api = 'api', | |||
| Team = 'team', | |||
| MCP = 'mcp', | |||
| Logout = 'logout', | |||
| } | |||
| @@ -39,10 +39,14 @@ export const useNavigatePage = () => { | |||
| navigate(Routes.Chat); | |||
| }, [navigate]); | |||
| const navigateToAgentList = useCallback(() => { | |||
| const navigateToAgents = useCallback(() => { | |||
| navigate(Routes.Agents); | |||
| }, [navigate]); | |||
| const navigateToAgentList = useCallback(() => { | |||
| navigate(Routes.AgentList); | |||
| }, [navigate]); | |||
| const navigateToAgent = useCallback( | |||
| (id: string) => () => { | |||
| navigate(`${Routes.Agent}/${id}`); | |||
| @@ -114,11 +118,12 @@ export const useNavigatePage = () => { | |||
| navigateToChunkParsedResult, | |||
| getQueryString, | |||
| navigateToChunk, | |||
| navigateToAgentList, | |||
| navigateToAgents, | |||
| navigateToAgent, | |||
| navigateToAgentTemplates, | |||
| navigateToSearchList, | |||
| navigateToSearch, | |||
| navigateToFiles, | |||
| navigateToAgentList, | |||
| }; | |||
| }; | |||
| @@ -35,6 +35,7 @@ export const enum AgentApiAction { | |||
| FetchInputForm = 'fetchInputForm', | |||
| FetchVersionList = 'fetchVersionList', | |||
| FetchVersion = 'fetchVersion', | |||
| FetchAgentAvatar = 'fetchAgentAvatar', | |||
| } | |||
| export const EmptyDsl = { | |||
| @@ -444,3 +445,32 @@ export const useFetchVersion = ( | |||
| return { data, loading }; | |||
| }; | |||
| export const useFetchAgentAvatar = (): { | |||
| data: IFlow; | |||
| loading: boolean; | |||
| refetch: () => void; | |||
| } => { | |||
| const { sharedId } = useGetSharedChatSearchParams(); | |||
| const { | |||
| data, | |||
| isFetching: loading, | |||
| refetch, | |||
| } = useQuery({ | |||
| queryKey: [AgentApiAction.FetchAgentAvatar], | |||
| initialData: {} as IFlow, | |||
| refetchOnReconnect: false, | |||
| refetchOnMount: false, | |||
| refetchOnWindowFocus: false, | |||
| gcTime: 0, | |||
| queryFn: async () => { | |||
| if (!sharedId) return {}; | |||
| const { data } = await agentService.fetchAgentAvatar(sharedId); | |||
| return data?.data ?? {}; | |||
| }, | |||
| }); | |||
| return { data, loading, refetch }; | |||
| }; | |||
| @@ -29,7 +29,7 @@ const RagHeader = () => { | |||
| { path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon }, | |||
| { path: '/chat', name: t('chat'), icon: MessageOutlined }, | |||
| { path: '/search', name: t('search'), icon: SearchOutlined }, | |||
| { path: '/flow', name: t('flow'), icon: GraphIcon }, | |||
| { path: '/agent-list', name: t('flow'), icon: GraphIcon }, | |||
| { path: '/file', name: t('fileManager'), icon: FileIcon }, | |||
| ], | |||
| [t], | |||
| @@ -732,6 +732,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s | |||
| view: 'View', | |||
| modelsToBeAddedTooltip: | |||
| 'If your model provider is not listed but claims to be "OpenAI-compatible", select the OpenAI-API-compatible card to add the relevant model(s). ', | |||
| mcp: 'MCP', | |||
| }, | |||
| message: { | |||
| registered: 'Registered!', | |||
| @@ -1,8 +1,7 @@ | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { useGetFileIcon } from '@/pages/chat/hooks'; | |||
| import { Spin } from 'antd'; | |||
| import { useSendNextMessage } from './hooks'; | |||
| import { useSendAgentMessage } from './use-send-agent-message'; | |||
| import MessageInput from '@/components/message-input'; | |||
| import MessageItem from '@/components/next-message-item'; | |||
| @@ -25,13 +24,12 @@ const AgentChatBox = () => { | |||
| handleInputChange, | |||
| handlePressEnter, | |||
| value, | |||
| loading, | |||
| ref, | |||
| derivedMessages, | |||
| stopOutputMessage, | |||
| sendFormMessage, | |||
| findReferenceByMessageId, | |||
| } = useSendNextMessage(); | |||
| } = useSendAgentMessage(); | |||
| const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | |||
| useClickDrawer(); | |||
| @@ -73,36 +71,36 @@ const AgentChatBox = () => { | |||
| <section className="flex flex-1 flex-col px-5 h-[90vh]"> | |||
| <div className="flex-1 overflow-auto"> | |||
| <div> | |||
| <Spin spinning={loading}> | |||
| {derivedMessages?.map((message, i) => { | |||
| return ( | |||
| <MessageItem | |||
| loading={ | |||
| message.role === MessageType.Assistant && | |||
| sendLoading && | |||
| derivedMessages.length - 1 === i | |||
| } | |||
| key={buildMessageUuidWithRole(message)} | |||
| nickname={userInfo.nickname} | |||
| avatar={userInfo.avatar} | |||
| avatarDialog={canvasInfo.avatar} | |||
| item={message} | |||
| reference={findReferenceByMessageId(message.id)} | |||
| clickDocumentButton={clickDocumentButton} | |||
| index={i} | |||
| showLikeButton={false} | |||
| sendLoading={sendLoading} | |||
| > | |||
| <DebugContent | |||
| parameters={buildInputList(message)} | |||
| ok={handleOk(message)} | |||
| isNext={false} | |||
| btnText={'Submit'} | |||
| ></DebugContent> | |||
| </MessageItem> | |||
| ); | |||
| })} | |||
| </Spin> | |||
| {/* <Spin spinning={sendLoading}> */} | |||
| {derivedMessages?.map((message, i) => { | |||
| return ( | |||
| <MessageItem | |||
| loading={ | |||
| message.role === MessageType.Assistant && | |||
| sendLoading && | |||
| derivedMessages.length - 1 === i | |||
| } | |||
| key={buildMessageUuidWithRole(message)} | |||
| nickname={userInfo.nickname} | |||
| avatar={userInfo.avatar} | |||
| avatarDialog={canvasInfo.avatar} | |||
| item={message} | |||
| reference={findReferenceByMessageId(message.id)} | |||
| clickDocumentButton={clickDocumentButton} | |||
| index={i} | |||
| showLikeButton={false} | |||
| sendLoading={sendLoading} | |||
| > | |||
| <DebugContent | |||
| parameters={buildInputList(message)} | |||
| ok={handleOk(message)} | |||
| isNext={false} | |||
| btnText={'Submit'} | |||
| ></DebugContent> | |||
| </MessageItem> | |||
| ); | |||
| })} | |||
| {/* </Spin> */} | |||
| </div> | |||
| <div ref={ref} /> | |||
| </div> | |||
| @@ -4,7 +4,6 @@ import { | |||
| useHandleMessageInputChange, | |||
| useSelectDerivedMessages, | |||
| } from '@/hooks/logic-hooks'; | |||
| import { useFetchAgent } from '@/hooks/use-agent-request'; | |||
| import { | |||
| IEventList, | |||
| IInputEvent, | |||
| @@ -30,37 +29,7 @@ import { BeginQuery } from '../interface'; | |||
| import useGraphStore from '../store'; | |||
| import { receiveMessageError } from '../utils'; | |||
| export const useSelectNextMessages = () => { | |||
| const { data: flowDetail, loading } = useFetchAgent(); | |||
| const reference = flowDetail.dsl.retrieval; | |||
| const { | |||
| derivedMessages, | |||
| ref, | |||
| addNewestQuestion, | |||
| addNewestAnswer, | |||
| removeLatestMessage, | |||
| removeMessageById, | |||
| removeMessagesAfterCurrentMessage, | |||
| addNewestOneQuestion, | |||
| addNewestOneAnswer, | |||
| } = useSelectDerivedMessages(); | |||
| return { | |||
| reference, | |||
| loading, | |||
| derivedMessages, | |||
| ref, | |||
| addNewestQuestion, | |||
| addNewestAnswer, | |||
| removeLatestMessage, | |||
| removeMessageById, | |||
| addNewestOneQuestion, | |||
| addNewestOneAnswer, | |||
| removeMessagesAfterCurrentMessage, | |||
| }; | |||
| }; | |||
| function findMessageFromList(eventList: IEventList) { | |||
| export function findMessageFromList(eventList: IEventList) { | |||
| const messageEventList = eventList.filter( | |||
| (x) => x.event === MessageEventType.Message, | |||
| ) as IMessageEvent[]; | |||
| @@ -101,7 +70,7 @@ function findMessageFromList(eventList: IEventList) { | |||
| }; | |||
| } | |||
| function findInputFromList(eventList: IEventList) { | |||
| export function findInputFromList(eventList: IEventList) { | |||
| const inputEvent = eventList.find( | |||
| (x) => x.event === MessageEventType.UserInputs, | |||
| ) as IInputEvent; | |||
| @@ -120,7 +89,7 @@ export function getLatestError(eventList: IEventList) { | |||
| return get(eventList.at(-1), 'data.outputs._ERROR'); | |||
| } | |||
| const useGetBeginNodePrologue = () => { | |||
| export const useGetBeginNodePrologue = () => { | |||
| const getNode = useGraphStore((state) => state.getNode); | |||
| return useMemo(() => { | |||
| @@ -131,31 +100,61 @@ const useGetBeginNodePrologue = () => { | |||
| }, [getNode]); | |||
| }; | |||
| export const useSendNextMessage = () => { | |||
| export function useFindMessageReference(answerList: IEventList) { | |||
| const [messageEndEventList, setMessageEndEventList] = useState< | |||
| IMessageEndEvent[] | |||
| >([]); | |||
| const findReferenceByMessageId = useCallback( | |||
| (messageId: string) => { | |||
| const event = messageEndEventList.find( | |||
| (item) => item.message_id === messageId, | |||
| ); | |||
| if (event) { | |||
| return (event?.data as IMessageEndData)?.reference; | |||
| } | |||
| }, | |||
| [messageEndEventList], | |||
| ); | |||
| useEffect(() => { | |||
| const messageEndEvent = answerList.find( | |||
| (x) => x.event === MessageEventType.MessageEnd, | |||
| ); | |||
| if (messageEndEvent) { | |||
| setMessageEndEventList((list) => { | |||
| const nextList = [...list]; | |||
| if ( | |||
| nextList.every((x) => x.message_id !== messageEndEvent.message_id) | |||
| ) { | |||
| nextList.push(messageEndEvent as IMessageEndEvent); | |||
| } | |||
| return nextList; | |||
| }); | |||
| } | |||
| }, [answerList]); | |||
| return { findReferenceByMessageId }; | |||
| } | |||
| export const useSendAgentMessage = (url?: string) => { | |||
| const { id: agentId } = useParams(); | |||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | |||
| const inputs = useSelectBeginNodeDataInputs(); | |||
| const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE( | |||
| url || api.runCanvas, | |||
| ); | |||
| const { findReferenceByMessageId } = useFindMessageReference(answerList); | |||
| const prologue = useGetBeginNodePrologue(); | |||
| const { | |||
| reference, | |||
| loading, | |||
| derivedMessages, | |||
| ref, | |||
| removeLatestMessage, | |||
| removeMessageById, | |||
| addNewestOneQuestion, | |||
| addNewestOneAnswer, | |||
| } = useSelectNextMessages(); | |||
| const { id: agentId } = useParams(); | |||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | |||
| const { refetch } = useFetchAgent(); | |||
| } = useSelectDerivedMessages(); | |||
| const { addEventList } = useContext(AgentChatLogContext); | |||
| const inputs = useSelectBeginNodeDataInputs(); | |||
| const [messageEndEventList, setMessageEndEventList] = useState< | |||
| IMessageEndEvent[] | |||
| >([]); | |||
| const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE( | |||
| api.runCanvas, | |||
| ); | |||
| const prologue = useGetBeginNodePrologue(); | |||
| const sendMessage = useCallback( | |||
| async ({ message }: { message: Message; messages?: Message[] }) => { | |||
| @@ -182,35 +181,40 @@ export const useSendNextMessage = () => { | |||
| setValue(message.content); | |||
| removeLatestMessage(); | |||
| } else { | |||
| refetch(); // pull the message list after sending the message successfully | |||
| // refetch(); // pull the message list after sending the message successfully | |||
| } | |||
| }, | |||
| [agentId, send, inputs, setValue, removeLatestMessage, refetch], | |||
| [agentId, send, inputs, setValue, removeLatestMessage], | |||
| ); | |||
| const handleSendMessage = useCallback( | |||
| async (message: Message) => { | |||
| sendMessage({ message }); | |||
| const sendFormMessage = useCallback( | |||
| (body: { id?: string; inputs: Record<string, BeginQuery> }) => { | |||
| send(body); | |||
| addNewestOneQuestion({ | |||
| content: Object.entries(body.inputs) | |||
| .map(([key, val]) => `${key}: ${val.value}`) | |||
| .join('<br/>'), | |||
| role: MessageType.User, | |||
| }); | |||
| }, | |||
| [sendMessage], | |||
| [addNewestOneQuestion, send], | |||
| ); | |||
| useEffect(() => { | |||
| const messageEndEvent = answerList.find( | |||
| (x) => x.event === MessageEventType.MessageEnd, | |||
| ); | |||
| if (messageEndEvent) { | |||
| setMessageEndEventList((list) => { | |||
| const nextList = [...list]; | |||
| if ( | |||
| nextList.every((x) => x.message_id !== messageEndEvent.message_id) | |||
| ) { | |||
| nextList.push(messageEndEvent as IMessageEndEvent); | |||
| } | |||
| return nextList; | |||
| const handlePressEnter = useCallback(() => { | |||
| if (trim(value) === '') return; | |||
| const id = uuid(); | |||
| if (done) { | |||
| setValue(''); | |||
| sendMessage({ | |||
| message: { id, content: value.trim(), role: MessageType.User }, | |||
| }); | |||
| } | |||
| }, [addEventList.length, answerList]); | |||
| addNewestOneQuestion({ | |||
| content: value, | |||
| id, | |||
| role: MessageType.User, | |||
| }); | |||
| }, [value, done, addNewestOneQuestion, setValue, sendMessage]); | |||
| useEffect(() => { | |||
| const { content, id } = findMessageFromList(answerList); | |||
| @@ -224,45 +228,6 @@ export const useSendNextMessage = () => { | |||
| } | |||
| }, [answerList, addNewestOneAnswer]); | |||
| const handlePressEnter = useCallback(() => { | |||
| if (trim(value) === '') return; | |||
| const id = uuid(); | |||
| if (done) { | |||
| setValue(''); | |||
| handleSendMessage({ id, content: value.trim(), role: MessageType.User }); | |||
| } | |||
| addNewestOneQuestion({ | |||
| content: value, | |||
| id, | |||
| role: MessageType.User, | |||
| }); | |||
| }, [value, done, addNewestOneQuestion, setValue, handleSendMessage]); | |||
| const sendFormMessage = useCallback( | |||
| (body: { id?: string; inputs: Record<string, BeginQuery> }) => { | |||
| send(body); | |||
| addNewestOneQuestion({ | |||
| content: Object.entries(body.inputs) | |||
| .map(([key, val]) => `${key}: ${val.value}`) | |||
| .join('<br/>'), | |||
| role: MessageType.User, | |||
| }); | |||
| }, | |||
| [addNewestOneQuestion, send], | |||
| ); | |||
| const findReferenceByMessageId = useCallback( | |||
| (messageId: string) => { | |||
| const event = messageEndEventList.find( | |||
| (item) => item.message_id === messageId, | |||
| ); | |||
| if (event) { | |||
| return (event?.data as IMessageEndData)?.reference; | |||
| } | |||
| }, | |||
| [messageEndEventList], | |||
| ); | |||
| useEffect(() => { | |||
| if (prologue) { | |||
| addNewestOneAnswer({ | |||
| @@ -272,7 +237,9 @@ export const useSendNextMessage = () => { | |||
| }, [addNewestOneAnswer, agentId, prologue, send, sendFormMessage]); | |||
| useEffect(() => { | |||
| addEventList(answerList); | |||
| if (typeof addEventList === 'function') { | |||
| addEventList(answerList); | |||
| } | |||
| }, [addEventList, answerList]); | |||
| return { | |||
| @@ -280,8 +247,6 @@ export const useSendNextMessage = () => { | |||
| handleInputChange, | |||
| value, | |||
| sendLoading: !done, | |||
| reference, | |||
| loading, | |||
| derivedMessages, | |||
| ref, | |||
| removeMessageById, | |||
| @@ -68,7 +68,7 @@ function EmbedDialog({ | |||
| const generateIframeSrc = useCallback(() => { | |||
| const { visibleAvatar, locale } = values; | |||
| let src = `${location.origin}/chat/share?shared_id=${token}&from=${from}&auth=${beta}`; | |||
| let src = `${location.origin}/next-chat/share?shared_id=${token}&from=${from}&auth=${beta}`; | |||
| if (visibleAvatar) { | |||
| src += '&visible_avatar=1'; | |||
| } | |||
| @@ -18,6 +18,7 @@ import { | |||
| import { SharedFrom } from '@/constants/chat'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | |||
| import { ReactFlowProvider } from '@xyflow/react'; | |||
| import { | |||
| ChevronDown, | |||
| @@ -37,7 +38,10 @@ import AgentCanvas from './canvas'; | |||
| import EmbedDialog from './embed-dialog'; | |||
| import { useHandleExportOrImportJsonFile } from './hooks/use-export-json'; | |||
| import { useFetchDataOnMount } from './hooks/use-fetch-data'; | |||
| import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query'; | |||
| import { | |||
| useGetBeginNodeDataInputs, | |||
| useGetBeginNodeDataQueryIsSafe, | |||
| } from './hooks/use-get-begin-query'; | |||
| import { useOpenDocument } from './hooks/use-open-document'; | |||
| import { | |||
| useSaveGraph, | |||
| @@ -67,6 +71,8 @@ export default function Agent() { | |||
| showModal: showChatDrawer, | |||
| } = useSetModalState(); | |||
| const { t } = useTranslation(); | |||
| const { data: userInfo } = useFetchUserInfo(); | |||
| const openDocument = useOpenDocument(); | |||
| const { | |||
| handleExportJson, | |||
| @@ -76,7 +82,7 @@ export default function Agent() { | |||
| hideFileUploadModal, | |||
| } = useHandleExportOrImportJsonFile(); | |||
| const { saveGraph, loading } = useSaveGraph(); | |||
| const { flowDetail } = useFetchDataOnMount(); | |||
| const { flowDetail: agentDetail } = useFetchDataOnMount(); | |||
| const inputs = useGetBeginNodeDataInputs(); | |||
| const { handleRun } = useSaveGraphBeforeOpeningDebugDrawer(showChatDrawer); | |||
| const handleRunAgent = useCallback(() => { | |||
| @@ -95,6 +101,8 @@ export default function Agent() { | |||
| const { showEmbedModal, hideEmbedModal, embedVisible, beta } = | |||
| useShowEmbedModal(); | |||
| const isBeginNodeDataQuerySafe = useGetBeginNodeDataQueryIsSafe(); | |||
| return ( | |||
| <section className="h-full"> | |||
| <PageHeader> | |||
| @@ -107,7 +115,7 @@ export default function Agent() { | |||
| </BreadcrumbItem> | |||
| <BreadcrumbSeparator /> | |||
| <BreadcrumbItem> | |||
| <BreadcrumbPage>{flowDetail.title}</BreadcrumbPage> | |||
| <BreadcrumbPage>{agentDetail.title}</BreadcrumbPage> | |||
| </BreadcrumbItem> | |||
| </BreadcrumbList> | |||
| </Breadcrumb> | |||
| @@ -154,7 +162,13 @@ export default function Agent() { | |||
| {t('flow.export')} | |||
| </AgentDropdownMenuItem> | |||
| <DropdownMenuSeparator /> | |||
| <AgentDropdownMenuItem onClick={showEmbedModal}> | |||
| <AgentDropdownMenuItem | |||
| onClick={showEmbedModal} | |||
| disabled={ | |||
| !isBeginNodeDataQuerySafe || | |||
| userInfo.nickname !== agentDetail.nickname | |||
| } | |||
| > | |||
| <ScreenShare /> | |||
| {t('common.embedIntoSite')} | |||
| </AgentDropdownMenuItem> | |||
| @@ -1,4 +1,12 @@ | |||
| import { PageHeader } from '@/components/page-header'; | |||
| import { | |||
| Breadcrumb, | |||
| BreadcrumbItem, | |||
| BreadcrumbLink, | |||
| BreadcrumbList, | |||
| BreadcrumbPage, | |||
| BreadcrumbSeparator, | |||
| } from '@/components/ui/breadcrumb'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request'; | |||
| @@ -57,10 +65,21 @@ export default function AgentTemplates() { | |||
| return ( | |||
| <section> | |||
| <PageHeader | |||
| back={navigateToAgentList} | |||
| title={t('flow.createGraph')} | |||
| ></PageHeader> | |||
| <PageHeader> | |||
| <Breadcrumb> | |||
| <BreadcrumbList> | |||
| <BreadcrumbItem> | |||
| <BreadcrumbLink onClick={navigateToAgentList}> | |||
| Agent | |||
| </BreadcrumbLink> | |||
| </BreadcrumbItem> | |||
| <BreadcrumbSeparator /> | |||
| <BreadcrumbItem> | |||
| <BreadcrumbPage>{t('flow.createGraph')}</BreadcrumbPage> | |||
| </BreadcrumbItem> | |||
| </BreadcrumbList> | |||
| </Breadcrumb> | |||
| </PageHeader> | |||
| <div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8 max-h-[94vh] overflow-auto px-8"> | |||
| {list?.map((x) => { | |||
| return ( | |||
| @@ -1,20 +1,7 @@ | |||
| import { MessageType, SharedFrom } from '@/constants/chat'; | |||
| import { useCreateNextSharedConversation } from '@/hooks/chat-hooks'; | |||
| import { | |||
| useHandleMessageInputChange, | |||
| useSelectDerivedMessages, | |||
| useSendMessageWithSse, | |||
| } from '@/hooks/logic-hooks'; | |||
| import { Message } from '@/interfaces/database/chat'; | |||
| import { message } from 'antd'; | |||
| import { get } from 'lodash'; | |||
| import { SharedFrom } from '@/constants/chat'; | |||
| import { useSendAgentMessage } from '@/pages/agent/chat/use-send-agent-message'; | |||
| import trim from 'lodash/trim'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import { useSearchParams } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| const isCompletionError = (res: any) => | |||
| res && (res?.response.status !== 200 || res?.data?.code !== 0); | |||
| export const useSendButtonDisabled = (value: string) => { | |||
| return trim(value) === ''; | |||
| @@ -40,110 +27,14 @@ export const useGetSharedChatSearchParams = () => { | |||
| }; | |||
| }; | |||
| export const useSendSharedMessage = () => { | |||
| const { | |||
| from, | |||
| sharedId: conversationId, | |||
| data: data, | |||
| } = useGetSharedChatSearchParams(); | |||
| const { createSharedConversation: setConversation } = | |||
| useCreateNextSharedConversation(); | |||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | |||
| const { send, answer, done, stopOutputMessage } = useSendMessageWithSse( | |||
| `/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`, | |||
| ); | |||
| const { | |||
| derivedMessages, | |||
| ref, | |||
| removeLatestMessage, | |||
| addNewestAnswer, | |||
| addNewestQuestion, | |||
| } = useSelectDerivedMessages(); | |||
| const [hasError, setHasError] = useState(false); | |||
| const sendMessage = useCallback( | |||
| async (message: Message, id?: string) => { | |||
| const res = await send({ | |||
| conversation_id: id ?? conversationId, | |||
| quote: true, | |||
| question: message.content, | |||
| session_id: get(derivedMessages, '0.session_id'), | |||
| }); | |||
| export function useSendNextSharedMessage() { | |||
| const { from, sharedId: conversationId } = useGetSharedChatSearchParams(); | |||
| const url = `/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`; | |||
| if (isCompletionError(res)) { | |||
| // cancel loading | |||
| setValue(message.content); | |||
| removeLatestMessage(); | |||
| } | |||
| }, | |||
| [send, conversationId, derivedMessages, setValue, removeLatestMessage], | |||
| ); | |||
| const handleSendMessage = useCallback( | |||
| async (message: Message) => { | |||
| if (conversationId !== '') { | |||
| sendMessage(message); | |||
| } else { | |||
| const data = await setConversation('user id'); | |||
| if (data.code === 0) { | |||
| const id = data.data.id; | |||
| sendMessage(message, id); | |||
| } | |||
| } | |||
| }, | |||
| [conversationId, setConversation, sendMessage], | |||
| ); | |||
| const fetchSessionId = useCallback(async () => { | |||
| const payload = { question: '' }; | |||
| const ret = await send({ ...payload, ...data }); | |||
| if (isCompletionError(ret)) { | |||
| message.error(ret?.data.message); | |||
| setHasError(true); | |||
| } | |||
| }, [data, send]); | |||
| useEffect(() => { | |||
| fetchSessionId(); | |||
| }, [fetchSessionId, send]); | |||
| useEffect(() => { | |||
| if (answer.answer) { | |||
| addNewestAnswer(answer); | |||
| } | |||
| }, [answer, addNewestAnswer]); | |||
| const handlePressEnter = useCallback( | |||
| (documentIds: string[]) => { | |||
| if (trim(value) === '') return; | |||
| const id = uuid(); | |||
| if (done) { | |||
| setValue(''); | |||
| addNewestQuestion({ | |||
| content: value, | |||
| doc_ids: documentIds, | |||
| id, | |||
| role: MessageType.User, | |||
| }); | |||
| handleSendMessage({ | |||
| content: value.trim(), | |||
| id, | |||
| role: MessageType.User, | |||
| }); | |||
| } | |||
| }, | |||
| [addNewestQuestion, done, handleSendMessage, setValue, value], | |||
| ); | |||
| const ret = useSendAgentMessage(url); | |||
| return { | |||
| handlePressEnter, | |||
| handleInputChange, | |||
| value, | |||
| sendLoading: !done, | |||
| ref, | |||
| loading: false, | |||
| derivedMessages, | |||
| hasError, | |||
| stopOutputMessage, | |||
| ...ret, | |||
| hasError: false, | |||
| }; | |||
| }; | |||
| } | |||
| @@ -1,13 +0,0 @@ | |||
| .chatWrapper { | |||
| height: 100vh; | |||
| } | |||
| .chatContainer { | |||
| padding: 10px; | |||
| box-sizing: border-box; | |||
| height: 100%; | |||
| .messageContainer { | |||
| overflow-y: auto; | |||
| padding-right: 6px; | |||
| } | |||
| } | |||
| @@ -1,13 +1,115 @@ | |||
| import ChatContainer from './large'; | |||
| import MessageInput from '@/components/message-input'; | |||
| import MessageItem from '@/components/next-message-item'; | |||
| import PdfDrawer from '@/components/pdf-drawer'; | |||
| import { useClickDrawer } from '@/components/pdf-drawer/hooks'; | |||
| import { MessageType, SharedFrom } from '@/constants/chat'; | |||
| import { useFetchNextConversationSSE } from '@/hooks/chat-hooks'; | |||
| import { useFetchAgentAvatar } from '@/hooks/use-agent-request'; | |||
| import { cn } from '@/lib/utils'; | |||
| import i18n from '@/locales/config'; | |||
| import { useSendButtonDisabled } from '@/pages/chat/hooks'; | |||
| import { buildMessageUuidWithRole } from '@/utils/chat'; | |||
| import React, { forwardRef, useMemo } from 'react'; | |||
| import { | |||
| useGetSharedChatSearchParams, | |||
| useSendNextSharedMessage, | |||
| } from '../hooks/use-send-shared-message'; | |||
| import styles from './index.less'; | |||
| const ChatContainer = () => { | |||
| const { | |||
| sharedId: conversationId, | |||
| from, | |||
| locale, | |||
| visibleAvatar, | |||
| } = useGetSharedChatSearchParams(); | |||
| const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | |||
| useClickDrawer(); | |||
| const { | |||
| handlePressEnter, | |||
| handleInputChange, | |||
| value, | |||
| sendLoading, | |||
| ref, | |||
| derivedMessages, | |||
| hasError, | |||
| stopOutputMessage, | |||
| findReferenceByMessageId, | |||
| } = useSendNextSharedMessage(); | |||
| const sendDisabled = useSendButtonDisabled(value); | |||
| const useFetchAvatar = useMemo(() => { | |||
| return from === SharedFrom.Agent | |||
| ? useFetchAgentAvatar | |||
| : useFetchNextConversationSSE; | |||
| }, [from]); | |||
| React.useEffect(() => { | |||
| if (locale && i18n.language !== locale) { | |||
| i18n.changeLanguage(locale); | |||
| } | |||
| }, [locale, visibleAvatar]); | |||
| const { data: avatarData } = useFetchAvatar(); | |||
| if (!conversationId) { | |||
| return <div>empty</div>; | |||
| } | |||
| const SharedChat = () => { | |||
| return ( | |||
| <div className={styles.chatWrapper}> | |||
| <ChatContainer></ChatContainer> | |||
| </div> | |||
| <section className="h-[100vh]"> | |||
| <section className={cn('flex flex-1 flex-col p-2.5 h-full')}> | |||
| <div className={cn('flex flex-1 flex-col overflow-auto pr-2')}> | |||
| <div> | |||
| {derivedMessages?.map((message, i) => { | |||
| return ( | |||
| <MessageItem | |||
| visibleAvatar={visibleAvatar} | |||
| key={buildMessageUuidWithRole(message)} | |||
| avatarDialog={avatarData.avatar} | |||
| item={message} | |||
| nickname="You" | |||
| reference={findReferenceByMessageId(message.id)} | |||
| loading={ | |||
| message.role === MessageType.Assistant && | |||
| sendLoading && | |||
| derivedMessages?.length - 1 === i | |||
| } | |||
| index={i} | |||
| clickDocumentButton={clickDocumentButton} | |||
| showLikeButton={false} | |||
| showLoudspeaker={false} | |||
| showLog={false} | |||
| ></MessageItem> | |||
| ); | |||
| })} | |||
| </div> | |||
| <div ref={ref} /> | |||
| </div> | |||
| <MessageInput | |||
| isShared | |||
| value={value} | |||
| disabled={hasError} | |||
| sendDisabled={sendDisabled} | |||
| conversationId={conversationId} | |||
| onInputChange={handleInputChange} | |||
| onPressEnter={handlePressEnter} | |||
| sendLoading={sendLoading} | |||
| uploadMethod="external_upload_and_parse" | |||
| showUploadIcon={false} | |||
| stopOutputMessage={stopOutputMessage} | |||
| ></MessageInput> | |||
| </section> | |||
| {visible && ( | |||
| <PdfDrawer | |||
| visible={visible} | |||
| hideModal={hideModal} | |||
| documentId={documentId} | |||
| chunk={selectedChunk} | |||
| ></PdfDrawer> | |||
| )} | |||
| </section> | |||
| ); | |||
| }; | |||
| export default SharedChat; | |||
| export default forwardRef(ChatContainer); | |||
| @@ -1,123 +0,0 @@ | |||
| import MessageInput from '@/components/message-input'; | |||
| import MessageItem from '@/components/message-item'; | |||
| import PdfDrawer from '@/components/pdf-drawer'; | |||
| import { useClickDrawer } from '@/components/pdf-drawer/hooks'; | |||
| import { MessageType, SharedFrom } from '@/constants/chat'; | |||
| import { useFetchNextConversationSSE } from '@/hooks/chat-hooks'; | |||
| import { useFetchFlowSSE } from '@/hooks/flow-hooks'; | |||
| import i18n from '@/locales/config'; | |||
| import { useSendButtonDisabled } from '@/pages/chat/hooks'; | |||
| import { buildMessageUuidWithRole } from '@/utils/chat'; | |||
| import { Flex, Spin } from 'antd'; | |||
| import React, { forwardRef, useMemo } from 'react'; | |||
| import { | |||
| useGetSharedChatSearchParams, | |||
| useSendSharedMessage, | |||
| } from '../hooks/use-send-shared-message'; | |||
| import { buildMessageItemReference } from '../utils'; | |||
| import styles from './index.less'; | |||
| const ChatContainer = () => { | |||
| const { | |||
| sharedId: conversationId, | |||
| from, | |||
| locale, | |||
| visibleAvatar, | |||
| } = useGetSharedChatSearchParams(); | |||
| const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | |||
| useClickDrawer(); | |||
| const { | |||
| handlePressEnter, | |||
| handleInputChange, | |||
| value, | |||
| sendLoading, | |||
| loading, | |||
| ref, | |||
| derivedMessages, | |||
| hasError, | |||
| stopOutputMessage, | |||
| } = useSendSharedMessage(); | |||
| const sendDisabled = useSendButtonDisabled(value); | |||
| const useFetchAvatar = useMemo(() => { | |||
| return from === SharedFrom.Agent | |||
| ? useFetchFlowSSE | |||
| : useFetchNextConversationSSE; | |||
| }, [from]); | |||
| React.useEffect(() => { | |||
| if (locale && i18n.language !== locale) { | |||
| i18n.changeLanguage(locale); | |||
| } | |||
| }, [locale, visibleAvatar]); | |||
| const { data: avatarData } = useFetchAvatar(); | |||
| if (!conversationId) { | |||
| return <div>empty</div>; | |||
| } | |||
| return ( | |||
| <> | |||
| <Flex flex={1} className={styles.chatContainer} vertical> | |||
| <Flex flex={1} vertical className={styles.messageContainer}> | |||
| <div> | |||
| <Spin spinning={loading}> | |||
| {derivedMessages?.map((message, i) => { | |||
| return ( | |||
| <MessageItem | |||
| visibleAvatar={visibleAvatar} | |||
| key={buildMessageUuidWithRole(message)} | |||
| avatarDialog={avatarData?.avatar} | |||
| item={message} | |||
| nickname="You" | |||
| reference={buildMessageItemReference( | |||
| { | |||
| message: derivedMessages, | |||
| reference: [], | |||
| }, | |||
| message, | |||
| )} | |||
| loading={ | |||
| message.role === MessageType.Assistant && | |||
| sendLoading && | |||
| derivedMessages?.length - 1 === i | |||
| } | |||
| index={i} | |||
| clickDocumentButton={clickDocumentButton} | |||
| showLikeButton={false} | |||
| showLoudspeaker={false} | |||
| ></MessageItem> | |||
| ); | |||
| })} | |||
| </Spin> | |||
| </div> | |||
| <div ref={ref} /> | |||
| </Flex> | |||
| <MessageInput | |||
| isShared | |||
| value={value} | |||
| disabled={hasError} | |||
| sendDisabled={sendDisabled} | |||
| conversationId={conversationId} | |||
| onInputChange={handleInputChange} | |||
| onPressEnter={handlePressEnter} | |||
| sendLoading={sendLoading} | |||
| uploadMethod="external_upload_and_parse" | |||
| showUploadIcon={false} | |||
| stopOutputMessage={stopOutputMessage} | |||
| ></MessageInput> | |||
| </Flex> | |||
| {visible && ( | |||
| <PdfDrawer | |||
| visible={visible} | |||
| hideModal={hideModal} | |||
| documentId={documentId} | |||
| chunk={selectedChunk} | |||
| ></PdfDrawer> | |||
| )} | |||
| </> | |||
| ); | |||
| }; | |||
| export default forwardRef(ChatContainer); | |||
| @@ -1,8 +1,7 @@ | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { EmptyConversationId, MessageType } from '@/constants/chat'; | |||
| import { IConversation, IReference } from '@/interfaces/database/chat'; | |||
| import { isEmpty } from 'lodash'; | |||
| import { EmptyConversationId } from './constants'; | |||
| import { IMessage } from './interface'; | |||
| import { IMessage } from '../chat/interface'; | |||
| export const isConversationIdExist = (conversationId: string) => { | |||
| return conversationId !== EmptyConversationId && conversationId !== ''; | |||
| @@ -6,6 +6,7 @@ import { | |||
| ProfileIcon, | |||
| TeamIcon, | |||
| } from '@/assets/icon/Icon'; | |||
| import { IconFont } from '@/components/icon-font'; | |||
| import { LLMFactory } from '@/constants/llm'; | |||
| import { UserSettingRouteKey } from '@/constants/setting'; | |||
| import { MonitorOutlined } from '@ant-design/icons'; | |||
| @@ -18,6 +19,9 @@ export const UserSettingIconMap = { | |||
| [UserSettingRouteKey.Team]: <TeamIcon />, | |||
| [UserSettingRouteKey.Logout]: <LogOutIcon />, | |||
| [UserSettingRouteKey.Api]: <ApiIcon />, | |||
| [UserSettingRouteKey.MCP]: ( | |||
| <IconFont name="mcp" className="size-6"></IconFont> | |||
| ), | |||
| }; | |||
| export * from '@/constants/setting'; | |||
| @@ -8,6 +8,7 @@ export enum Routes { | |||
| Agent = '/agent', | |||
| AgentTemplates = '/agent-templates', | |||
| Agents = '/agents', | |||
| AgentList = '/agent-list', | |||
| Searches = '/next-searches', | |||
| Search = '/next-search', | |||
| Chats = '/next-chats', | |||
| @@ -53,6 +54,11 @@ const routes = [ | |||
| component: '@/pages/chat/share', | |||
| layout: false, | |||
| }, | |||
| { | |||
| path: '/next-chat/share', | |||
| component: '@/pages/next-chats/share', | |||
| layout: false, | |||
| }, | |||
| { | |||
| path: '/', | |||
| component: '@/layouts', | |||
| @@ -135,6 +141,10 @@ const routes = [ | |||
| path: '/user-setting/api', | |||
| component: '@/pages/user-setting/setting-api', | |||
| }, | |||
| { | |||
| path: `/user-setting${Routes.Mcp}`, | |||
| component: `@/pages${Routes.ProfileMcp}`, | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -145,6 +155,10 @@ const routes = [ | |||
| path: '/flow', | |||
| component: '@/pages/flow/list', | |||
| }, | |||
| { | |||
| path: Routes.AgentList, | |||
| component: `@/pages/${Routes.Agents}`, | |||
| }, | |||
| { | |||
| path: '/flow/:id', | |||
| component: '@/pages/flow', | |||
| @@ -20,6 +20,7 @@ const { | |||
| fetchVersionList, | |||
| fetchVersion, | |||
| fetchCanvas, | |||
| fetchAgentAvatar, | |||
| } = api; | |||
| const methods = { | |||
| @@ -95,6 +96,10 @@ const methods = { | |||
| url: inputForm, | |||
| method: 'get', | |||
| }, | |||
| fetchAgentAvatar: { | |||
| url: fetchAgentAvatar, | |||
| method: 'get', | |||
| }, | |||
| } as const; | |||
| const agentService = registerNextServer<keyof typeof methods>(methods); | |||
| @@ -151,6 +151,7 @@ export default { | |||
| fetchVersionList: (id: string) => `${api_host}/canvas/getlistversion/${id}`, | |||
| fetchVersion: (id: string) => `${api_host}/canvas/getversion/${id}`, | |||
| fetchCanvas: (id: string) => `${api_host}/canvas/get/${id}`, | |||
| fetchAgentAvatar: (id: string) => `${api_host}/canvas/getsse/${id}`, | |||
| // mcp server | |||
| listMcpServer: `${api_host}/mcp_server/list`, | |||