Bläddra i källkod

Feat: Share agent dialog box externally #3221 (#9005)

### 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
balibabu 3 månader sedan
förälder
incheckning
5f0ec005ba
Inget konto är kopplat till bidragsgivarens mejladress

+ 1
- 1
web/public/iconfont.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 7
- 3
web/src/components/next-message-item/group-button.tsx Visa fil

@@ -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

+ 3
- 0
web/src/components/next-message-item/index.tsx Visa fil

@@ -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

+ 1
- 0
web/src/constants/setting.ts Visa fil

@@ -7,6 +7,7 @@ export enum UserSettingRouteKey {
System = 'system',
Api = 'api',
Team = 'team',
MCP = 'mcp',
Logout = 'logout',
}


+ 7
- 2
web/src/hooks/logic-hooks/navigate-hooks.ts Visa fil

@@ -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,
};
};

+ 30
- 0
web/src/hooks/use-agent-request.ts Visa fil

@@ -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 };
};

+ 1
- 1
web/src/layouts/components/header/index.tsx Visa fil

@@ -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],

+ 1
- 0
web/src/locales/en.ts Visa fil

@@ -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!',

+ 32
- 34
web/src/pages/agent/chat/box.tsx Visa fil

@@ -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>

web/src/pages/agent/chat/hooks.ts → web/src/pages/agent/chat/use-send-agent-message.ts Visa fil

@@ -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,

+ 1
- 1
web/src/pages/agent/embed-dialog/index.tsx Visa fil

@@ -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
- 4
web/src/pages/agent/index.tsx Visa fil

@@ -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>

+ 23
- 4
web/src/pages/agents/agent-templates.tsx Visa fil

@@ -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 (

+ 9
- 118
web/src/pages/next-chats/hooks/use-send-shared-message.ts Visa fil

@@ -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,
};
};
}

+ 0
- 13
web/src/pages/next-chats/share/index.less Visa fil

@@ -1,13 +0,0 @@
.chatWrapper {
height: 100vh;
}

.chatContainer {
padding: 10px;
box-sizing: border-box;
height: 100%;
.messageContainer {
overflow-y: auto;
padding-right: 6px;
}
}

+ 109
- 7
web/src/pages/next-chats/share/index.tsx Visa fil

@@ -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);

+ 0
- 123
web/src/pages/next-chats/share/large.tsx Visa fil

@@ -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);

+ 2
- 3
web/src/pages/next-chats/utils.ts Visa fil

@@ -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 !== '';

+ 4
- 0
web/src/pages/user-setting/constants.tsx Visa fil

@@ -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';

+ 14
- 0
web/src/routes.ts Visa fil

@@ -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',

+ 5
- 0
web/src/services/agent-service.ts Visa fil

@@ -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);

+ 1
- 0
web/src/utils/api.ts Visa fil

@@ -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`,

Laddar…
Avbryt
Spara