From 23917c7b3ed21d4d5c381d563d03dcb835612f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Mon, 29 Jun 2026 17:29:35 +0800 Subject: [PATCH] fix: agent app log detail modal not display well (#38014) --- .../app/log/__tests__/list.spec.tsx | 75 ++++++++++++++++++- web/app/components/app/log/list.tsx | 31 +++++++- .../agent-log-modal/__tests__/index.spec.tsx | 25 +++++++ .../components/base/agent-log-modal/index.tsx | 49 ++++++++++-- 4 files changed, 169 insertions(+), 11 deletions(-) diff --git a/web/app/components/app/log/__tests__/list.spec.tsx b/web/app/components/app/log/__tests__/list.spec.tsx index 68124616718..cc63b774edf 100644 --- a/web/app/components/app/log/__tests__/list.spec.tsx +++ b/web/app/components/app/log/__tests__/list.spec.tsx @@ -21,6 +21,7 @@ let mockChatConversationDetail: Record | undefined let mockCompletionConversationDetail: Record | undefined let mockShowMessageLogModal = false let mockShowPromptLogModal = false +let mockShowAgentLogModal = false let mockCurrentLogItem: Record | undefined let mockCurrentLogModalActiveTab = 'messages' @@ -81,6 +82,7 @@ vi.mock('@/app/components/app/store', () => ({ setShowAgentLogModal: mockSetShowAgentLogModal, setShowMessageLogModal: mockSetShowMessageLogModal, showPromptLogModal: mockShowPromptLogModal, + showAgentLogModal: mockShowAgentLogModal, currentLogModalActiveTab: mockCurrentLogModalActiveTab, }), })) @@ -126,6 +128,7 @@ vi.mock('@/app/components/base/chat/chat', () => ({ onAnnotationEdited, onAnnotationRemoved, switchSibling, + hideLogModal, }: { chatList: Array<{ id: string }> onFeedback: (mid: string, value: { rating: string, content?: string }) => Promise @@ -133,8 +136,9 @@ vi.mock('@/app/components/base/chat/chat', () => ({ onAnnotationEdited: (query: string, answer: string, index: number) => void onAnnotationRemoved: (index: number) => Promise switchSibling: (siblingMessageId: string) => void + hideLogModal?: boolean }) => ( -
+
{chatList.length}
@@ -145,6 +149,14 @@ vi.mock('@/app/components/base/chat/chat', () => ({ ), })) +vi.mock('@/app/components/base/agent-log-modal', () => ({ + default: ({ floating, onCancel }: { floating?: boolean, onCancel: () => void }) => ( +
+ +
+ ), +})) + vi.mock('@/app/components/base/message-log-modal', () => ({ default: ({ onCancel }: { onCancel: () => void }) => (
@@ -255,6 +267,7 @@ describe('ConversationList', () => { mockCompletionConversationDetail = undefined mockShowMessageLogModal = false mockShowPromptLogModal = false + mockShowAgentLogModal = false mockCurrentLogItem = undefined mockCurrentLogModalActiveTab = 'messages' mockDelAnnotation.mockResolvedValue(undefined) @@ -383,6 +396,7 @@ describe('ConversationList', () => { expect(screen.getByTestId('var-panel')).toHaveTextContent('query:Latest question') expect(screen.getByTestId('model-info')).toHaveTextContent('gpt-4o') + expect(screen.getByTestId('chat-panel')).toHaveAttribute('data-hide-log-modal', 'true') expect(screen.getByTestId('message-log-modal')).toBeInTheDocument() fireEvent.click(screen.getByText('chat-feedback')) @@ -399,6 +413,61 @@ describe('ConversationList', () => { }) }) + it('should mount agent log modals from the detail panel instead of the nested chat layout', async () => { + mockChatConversationDetail = { + id: 'conversation-1', + created_at: 1710000000, + model_config: { + model: 'gpt-4o', + configs: { + introduction: 'Hello there', + }, + user_input_form: [], + }, + message: { + inputs: {}, + }, + } + mockShowAgentLogModal = true + mockCurrentLogItem = { + id: 'message-1', + conversationId: 'conversation-1', + } + mockFetchChatMessages.mockResolvedValue({ + data: [ + { + id: 'message-1', + answer: 'Assistant reply', + query: 'Latest question', + created_at: 1710000000, + inputs: {}, + feedbacks: [], + message: [], + message_files: [], + agent_thoughts: [{ id: 'thought-1' }], + }, + ], + has_more: false, + }) + + renderConversationList({ + searchParams: '?page=2&conversation_id=conversation-1', + }) + + await waitFor(() => { + expect(screen.getByTestId('chat-panel')).toBeInTheDocument() + }) + + expect(screen.getByTestId('chat-panel')).toHaveAttribute('data-hide-log-modal', 'true') + expect(screen.getByTestId('agent-log-modal')).toBeInTheDocument() + expect(screen.getByTestId('agent-log-modal')).toHaveAttribute('data-floating', 'true') + + fireEvent.click(screen.getByText('close-agent-log-modal')) + + expect(mockSetCurrentLogItem).toHaveBeenCalled() + expect(mockSetShowAgentLogModal).toHaveBeenCalledWith(false) + }) + it('should render completion details and refetch after feedback updates', async () => { mockCompletionConversationDetail = { id: 'conversation-1', @@ -424,7 +493,7 @@ describe('ConversationList', () => { }, } mockShowPromptLogModal = true - mockCurrentLogItem = { id: 'log-2' } + mockCurrentLogItem = { id: 'log-2', log: [{ role: 'user', text: 'Prompt body' }] } renderConversationList({ appDetail: { id: 'app-1', mode: AppModeEnum.COMPLETION } as any, @@ -626,7 +695,7 @@ describe('ConversationList', () => { }, } mockShowPromptLogModal = true - mockCurrentLogItem = { id: 'log-2' } + mockCurrentLogItem = { id: 'log-2', log: [{ role: 'user', text: 'Prompt body' }] } renderConversationList({ appDetail: { id: 'app-1', mode: AppModeEnum.COMPLETION } as any, diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 315cddaa123..507d45029f9 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -36,6 +36,7 @@ import ModelInfo from '@/app/components/app/log/model-info' import { useStore as useAppStore } from '@/app/components/app/store' import TextGeneration from '@/app/components/app/text-generate/item' import ActionButton from '@/app/components/base/action-button' +import AgentLogModal from '@/app/components/base/agent-log-modal' import Chat from '@/app/components/base/chat/chat' import CopyIcon from '@/app/components/base/copy-icon' import Loading from '@/app/components/base/loading' @@ -165,13 +166,25 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { }) const { formatTime } = useTimestamp() const { onClose, appDetail } = useContext(DrawerContext) - const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, showPromptLogModal, setShowPromptLogModal, currentLogModalActiveTab } = useAppStore(useShallow((state: AppStoreState) => ({ + const { + currentLogItem, + setCurrentLogItem, + showMessageLogModal, + setShowMessageLogModal, + showPromptLogModal, + setShowPromptLogModal, + showAgentLogModal, + setShowAgentLogModal, + currentLogModalActiveTab, + } = useAppStore(useShallow((state: AppStoreState) => ({ currentLogItem: state.currentLogItem, setCurrentLogItem: state.setCurrentLogItem, showMessageLogModal: state.showMessageLogModal, setShowMessageLogModal: state.setShowMessageLogModal, showPromptLogModal: state.showPromptLogModal, setShowPromptLogModal: state.setShowPromptLogModal, + showAgentLogModal: state.showAgentLogModal, + setShowAgentLogModal: state.setShowAgentLogModal, currentLogModalActiveTab: state.currentLogModalActiveTab, }))) const { t } = useTranslation() @@ -395,6 +408,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { const isChatMode = appDetail?.mode !== AppModeEnum.COMPLETION const isAdvanced = appDetail?.mode === AppModeEnum.ADVANCED_CHAT + const shouldShowPromptLogModal = showPromptLogModal && !!currentLogItem?.log const varList = getDetailVarList(detail, varValues) const message_files = getCompletionMessageFiles(detail, isChatMode) @@ -507,6 +521,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { noChatInput showPromptLog hideProcessDetail + hideLogModal chatContainerInnerClassName="px-3" switchSibling={switchSibling} /> @@ -546,6 +561,7 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { noChatInput showPromptLog hideProcessDetail + hideLogModal chatContainerInnerClassName="px-3" switchSibling={switchSibling} /> @@ -574,7 +590,18 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) { /> )} - {!isChatMode && showPromptLogModal && ( + {showAgentLogModal && ( + { + setCurrentLogItem() + setShowAgentLogModal(false) + }} + /> + )} + {shouldShowPromptLogModal && ( { }) }) + it('should render the floating modal through a dialog portal', () => { + vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {})) + + const { container } = render() + + const modal = screen.getByRole('dialog') + expect(container).not.toContainElement(modal) + expect(document.body).toContainElement(modal) + expect(modal).toHaveClass('fixed', 'z-50', 'w-[480px]!', 'left-[max(8px,calc(100vw-1136px))]!') + }) + it('should call onCancel when close button is clicked', () => { vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {})) @@ -158,4 +169,18 @@ describe('AgentLogModal', () => { expect(mockProps.onCancel).not.toHaveBeenCalled() }) + + it('should not use click-away to close the floating dialog', () => { + vi.mocked(fetchAgentLogDetail).mockReturnValue(new Promise(() => {})) + + let clickAwayHandler!: (event: Event) => void + vi.mocked(useClickAway).mockImplementation((callback) => { + clickAwayHandler = callback + }) + + render() + clickAwayHandler(new Event('click')) + + expect(mockProps.onCancel).not.toHaveBeenCalled() + }) }) diff --git a/web/app/components/base/agent-log-modal/index.tsx b/web/app/components/base/agent-log-modal/index.tsx index 42c0250f8a9..30033bafacc 100644 --- a/web/app/components/base/agent-log-modal/index.tsx +++ b/web/app/components/base/agent-log-modal/index.tsx @@ -1,6 +1,7 @@ import type { FC } from 'react' import type { IChatItem } from '@/app/components/base/chat/chat/type' import { cn } from '@langgenius/dify-ui/cn' +import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' import { RiCloseLine } from '@remixicon/react' import { useClickAway } from 'ahooks' import { useEffect, useRef, useState } from 'react' @@ -10,11 +11,13 @@ import AgentLogDetail from './detail' type AgentLogModalProps = Readonly<{ currentLogItem?: IChatItem width: number + floating?: boolean onCancel: () => void }> const AgentLogModal: FC = ({ currentLogItem, width, + floating, onCancel, }) => { const { t } = useTranslation() @@ -22,7 +25,7 @@ const AgentLogModal: FC = ({ const [mounted, setMounted] = useState(false) useClickAway(() => { - if (mounted) + if (mounted && !floating) onCancel() }, ref) @@ -33,6 +36,44 @@ const AgentLogModal: FC = ({ if (!currentLogItem || !currentLogItem.conversationId) return null + const detailContent = ( + <> + + + ) + + if (floating) { + return ( + { + if (!open) + onCancel() + }} + > + + {t('runDetail.workflowTitle', { ns: 'appLog' })} + + {detailContent} + + + ) + } + return (
= ({ >
) }