diff --git a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicContent.tsx b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicContent.tsx index d14c6e9fb4e0..0fcb23225bf9 100644 --- a/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicContent.tsx +++ b/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/TopicContent.tsx @@ -14,6 +14,8 @@ import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Flexbox } from 'react-layout-kit'; +import BubblesLoading from '@/components/BubblesLoading'; +import { LOADING_FLAT } from '@/const/message'; import { useIsMobile } from '@/hooks/useIsMobile'; import { useChatStore } from '@/store/chat'; @@ -160,13 +162,19 @@ const TopicContent = memo(({ id, title, fav, showMore }) => { spin={isLoading} /> {!editing ? ( - - {title} - + title === LOADING_FLAT ? ( + + + + ) : ( + + {title} + + ) ) : ( ( - ({ arguments: requestArgs, messageId, index, identifier, style }) => { + ({ arguments: requestArgs, apiName, messageId, id, index, identifier, style }) => { const { t } = useTranslation('plugin'); const { styles } = useStyles(); const [open, setOpen] = useState(false); const loading = useChatStore(chatSelectors.isToolCallStreaming(messageId, index)); + const toolMessage = useChatStore(chatSelectors.getMessageByToolCallId(id)); + const isMobile = useIsMobile(); const pluginMeta = useToolStore(toolSelectors.getMetaById(identifier), isEqual); const pluginTitle = pluginHelpers.getPluginTitle(pluginMeta) ?? t('unknownPlugin'); - return ( + // when tool calling stop streaming, we should show the tool message + return !loading && toolMessage ? ( + + ) : ( { setOpen(!open); }} > - {loading ? : } - {pluginTitle} + {loading ? ( +
+ +
+ ) : ( + + )} + {isMobile ? ( + +
{pluginTitle}
+ + {apiName} + +
+ ) : ( + <> +
{pluginTitle}
+ {apiName} + + )}
-
- {(open || loading) && } + {loading && }
); }, diff --git a/src/features/Conversation/Messages/Assistant/ToolCalls/style.ts b/src/features/Conversation/Messages/Assistant/ToolCallItem/style.ts similarity index 50% rename from src/features/Conversation/Messages/Assistant/ToolCalls/style.ts rename to src/features/Conversation/Messages/Assistant/ToolCallItem/style.ts index 3cef48295aa4..9fccf9c8dcb6 100644 --- a/src/features/Conversation/Messages/Assistant/ToolCalls/style.ts +++ b/src/features/Conversation/Messages/Assistant/ToolCallItem/style.ts @@ -1,19 +1,30 @@ import { createStyles } from 'antd-style'; export const useStyles = createStyles(({ css, token }) => ({ + apiName: css` + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + + font-size: 12px; + text-overflow: ellipsis; + `, container: css` cursor: pointer; width: fit-content; - padding-inline: 4px 6px; + padding-block: 6px; + padding-inline: 8px; + padding-inline-end: 12px; color: ${token.colorText}; - background: ${token.colorFillTertiary}; + border: 1px solid ${token.colorBorder}; border-radius: 8px; &:hover { - background: ${token.colorFillSecondary}; + background: ${token.colorFillTertiary}; } `, plugin: css` diff --git a/src/features/Conversation/Messages/Assistant/index.tsx b/src/features/Conversation/Messages/Assistant/index.tsx index 1e4fdc2c07ec..b2bf0fde207a 100644 --- a/src/features/Conversation/Messages/Assistant/index.tsx +++ b/src/features/Conversation/Messages/Assistant/index.tsx @@ -7,7 +7,7 @@ import { chatSelectors } from '@/store/chat/selectors'; import { ChatMessage } from '@/types/message'; import { DefaultMessage } from '../Default'; -import ToolCall from './ToolCalls'; +import ToolCall from './ToolCallItem'; export const AssistantMessage = memo< ChatMessage & { @@ -31,17 +31,16 @@ export const AssistantMessage = memo< /> )} {!editing && tools && ( - + {tools.map((toolCall, index) => ( ))} diff --git a/src/features/Conversation/Messages/Default.tsx b/src/features/Conversation/Messages/Default.tsx index bc62fc16c7d1..67c9228c3380 100644 --- a/src/features/Conversation/Messages/Default.tsx +++ b/src/features/Conversation/Messages/Default.tsx @@ -3,7 +3,7 @@ import { ReactNode, memo } from 'react'; import { LOADING_FLAT } from '@/const/message'; import { ChatMessage } from '@/types/message'; -import BubblesLoading from '../components/BubblesLoading'; +import BubblesLoading from '@/components/BubblesLoading'; export const DefaultMessage = memo< ChatMessage & { diff --git a/src/features/Conversation/Messages/User.tsx b/src/features/Conversation/Messages/User.tsx index 76d186cb3c12..8e1d6a23d9b0 100644 --- a/src/features/Conversation/Messages/User.tsx +++ b/src/features/Conversation/Messages/User.tsx @@ -1,12 +1,11 @@ import { ReactNode, memo } from 'react'; import { Flexbox } from 'react-layout-kit'; +import BubblesLoading from '@/components/BubblesLoading'; import { LOADING_FLAT } from '@/const/message'; import { FileListPreviewer } from '@/features/FileList'; import { ChatMessage } from '@/types/message'; -import BubblesLoading from '../components/BubblesLoading'; - export const UserMessage = memo< ChatMessage & { editableContent: ReactNode; diff --git a/src/store/chat/slices/message/selectors.test.ts b/src/store/chat/slices/message/selectors.test.ts index 57f5238bb30e..130db8656cd1 100644 --- a/src/store/chat/slices/message/selectors.test.ts +++ b/src/store/chat/slices/message/selectors.test.ts @@ -131,6 +131,36 @@ describe('chatSelectors', () => { }); }); + describe('getMessageByToolCallId', () => { + it('should return undefined if the message with the given id does not exist', () => { + const message = chatSelectors.getMessageByToolCallId('non-existent-id')(initialStore); + expect(message).toBeUndefined(); + }); + + it('should return the message object with the matching tool_call_id', () => { + const toolMessage = { + id: 'msg3', + content: 'Function Message', + role: 'tool', + tool_call_id: 'ttt', + plugin: { + arguments: 'arg1', + identifier: 'func1', + apiName: 'ttt', + type: 'default', + }, + } as ChatMessage; + const state = merge(initialStore, { + messagesMap: { + [messageMapKey('abc')]: [...mockMessages, toolMessage], + }, + activeId: 'abc', + }); + const message = chatSelectors.getMessageByToolCallId('ttt')(state); + expect(message).toMatchObject(toolMessage); + }); + }); + describe('currentChatsWithHistoryConfig', () => { it('should slice the messages according to the current agent config', () => { const state = merge(initialStore, { @@ -203,7 +233,7 @@ describe('chatSelectors', () => { }); describe('currentChatsWithGuideMessage', () => { - it('should return existing messages if there are any', () => { + it('should return existing messages except tool message', () => { const state = merge(initialStore, { messagesMap: { [messageMapKey('someActiveId')]: mockMessages, @@ -211,7 +241,7 @@ describe('chatSelectors', () => { activeId: 'someActiveId', }); const chats = chatSelectors.currentChatsWithGuideMessage({} as MetaData)(state); - expect(chats).toEqual(mockedChats); + expect(chats).toEqual(mockedChats.slice(0, 2)); }); it('should add a guide message if the chat is brand new', () => { diff --git a/src/store/chat/slices/message/selectors.ts b/src/store/chat/slices/message/selectors.ts index 33f6ed824f26..50d5773f4b89 100644 --- a/src/store/chat/slices/message/selectors.ts +++ b/src/store/chat/slices/message/selectors.ts @@ -65,11 +65,12 @@ const showInboxWelcome = (s: ChatStore): boolean => { return isBrandNewChat; }; -// 针对新助手添加初始化时的自定义消息 +// Custom message for new assistant initialization const currentChatsWithGuideMessage = (meta: MetaData) => (s: ChatStore): ChatMessage[] => { - const data = currentChats(s); + // skip tool message + const data = currentChats(s).filter((m) => m.role !== 'tool'); const { isAgentEditable } = featureFlagsSelectors(createServerConfigStore().getState()); @@ -125,6 +126,10 @@ const chatsMessageString = (s: ChatStore): string => { const getMessageById = (id: string) => (s: ChatStore) => chatHelpers.getMessageById(currentChats(s), id); +const getMessageByToolCallId = (id: string) => (s: ChatStore) => { + const messages = currentChats(s); + return messages.find((m) => m.tool_call_id === id); +}; const getTraceIdByMessageId = (id: string) => (s: ChatStore) => getMessageById(id)(s)?.traceId; const latestMessage = (s: ChatStore) => currentChats(s).at(-1); @@ -160,6 +165,7 @@ export const chatSelectors = { currentChatsWithHistoryConfig, currentToolMessages, getMessageById, + getMessageByToolCallId, getTraceIdByMessageId, isAIGenerating, isCreatingMessage,